--- id: tunnels/producer-open-op name: Producer half — establisher (dial shape) + pump handler + register_tunnel_openable status: pending depends_on: [tunnels/params, tunnels/wire-codec] scope: broad risk: high impact: phase level: implementation tags: [producer, establisher, open-op, pump] --- ## Description Implement `src/producer.rs` per producer.md: the dial-shape establisher, the substrate-agnostic pump handler, and the registration surface. This is the crate's core — the open op that both POCs validated (the forward POC's producer on the accept side; the reverse POC's worker on the connect side). Port from the REVERSE POC first (`/workspace/alktunnels-reverse-poc/src/producer.rs`) — it is the 0.6/0.7 idiomatic shape (plan flow, no handoff) and its 16 tests are the reference suite. ### The establisher (dial shape — the listen shape is a separate task) ```rust pub type DialFn = Arc< dyn Fn(Substrate, &str) -> BoxFuture<'static, Result> + Send + Sync, >; // TargetHandle: boxed halves (read: Box, write: ...), // or the framed UDP adapter (the establisher wraps BEFORE returning the plan — // ADR-003's placement; the pump stays substrate-agnostic) ``` - `tunnel_establisher(registry: ResourceRegistry, dial: DialFn, identity_witness: Option<...>) -> OpenEstablisher` — the assembly layer injects the substrate dial function (ADR-004); the establisher: 1. Parses params (`TunnelParams` — schema-validated upstream, semantic parse here). 2. Registry lookup `(resource, substrate)` → backing address — `unknown_resource` on miss (typed reason mapping per ADR-001's table). 3. `dial(substrate, backing)` → the handle (the closure owns real socket code — behind `local` or assembly-constructed). 4. UDP: wrap in the framed adapter (ADR-003) — `UdpHalf` + codec, truncation fail-loud (OQ-TN-13). 5. Return `Ok(Establishment::new(Arc::new(handle)))` (R-01 plan flow). - `ResourceRegistry`: the assembly-owned `HashMap<(String, Substrate), String>` (POC shape; OQ-TN-11's collision domain) with `register`/`lookup` async methods. ### The pump handler ```rust pub fn make_tunnel_pump_handler() -> OpenHandler ``` 1. Downcast the plan to `TargetHandle` (`Arc::downcast` — error path: log + return; the birth-teardown telemetry catches it). 2. `conn.accept_bi()` (yield-once). 3. `pump_bidi(bidi, t_read, t_write)` — **awaited inline** (R-02: the returned `JoinHandle` tracks the data-plane lifetime; early return = teardown-at-birth). The handler is substrate-agnostic by construction — it must not know what the halves came from (ADR-004). ### Registration ```rust pub fn register_tunnel_openable( core: &ChannelCore, registry: &ResourceRegistry, on_registry: &Arc, // the session's dispatch registry (ADR-047 §4 fork) auth: AuthContext, dial: DialFn, ) -> Result<(), String> ``` - Wires `tunnel_open_spec()` + the establisher + the pump via `register_openable_with_establisher` (timeout `None` = the 10s default). - Post-hoc registration is supported (W2): the dispatcher reads through the shared `Arc` per dispatch. ### Integration tests (the POC suite, ported) Port the reverse POC's test topology (`harness.rs`: worker = `from_connection_with_serving` + `ChannelOperations::register_on` + post-hoc openable; hub = adapter + capturing install hook) into `tests/` with the `dial` closure standing in for real sockets (in-process duplex halves — no `local` feature needed). Cover: establishment success + typed errors (unknown_resource / dial_failed / FORBIDDEN / timeout-adjacent), the plan flow under same-resource concurrency, per-call opener identity (CF-006 witness), late-registration visibility, pump round-trip through `pump_bidi`. ## Acceptance Criteria - [ ] Establisher: typed reason mapping exact (ADR-001's table) - [ ] Pump handler: `pump_bidi` inline; no spawn-and-forget; no substrate types leak into the handler - [ ] `register_tunnel_openable` wires spec + establisher + pump + dial injection - [ ] Integration tests: forward AND reverse topology (the harness shapes from both POCs), ≥8 tests covering the above - [ ] Clippy/fmt clean; wasm32 check passes (dial closures are runtime-injected — the crate compiles without `local`) - [ ] `cargo test` green ## References - docs/architecture/producer.md (the normative shapes) - docs/architecture/decisions/001/003/004 (params, codec placement, no trait) - POC reference: `/workspace/alktunnels-reverse-poc/src/producer.rs` + `harness.rs` + `tests/tunnel_poc.rs` (the 0.7.0-idiomatic shapes) ## Notes > Agent fills during implementation. ## Summary > Agent fills this on completion.