--- id: tunnels/producer-open-op name: Producer half — establisher (dial shape) + pump handler + register_tunnel_openable status: completed 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 - [x] Establisher: typed reason mapping exact (ADR-001's table) - [x] Pump handler: `pump_bidi` inline; no spawn-and-forget; no substrate types leak into the handler - [x] `register_tunnel_openable` wires spec + establisher + pump + dial injection - [x] Integration tests: forward AND reverse topology (the harness shapes from both POCs), ≥8 tests covering the above - [x] Clippy/fmt clean; wasm32 check passes (dial closures are runtime-injected — the crate compiles without `local`) - [x] `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 - Ported from the reverse POC's producer.rs (the 0.7.0-idiomatic plan flow, R-01) with the crate generalizations: - The dial closure is INJECTED (`DialFn`) — the POC had real socket code inline (tokio TCP/UDP); the crate's establisher is substrate-agnostic and the `local` feature (or the assembly layer) supplies the closure (ADR-004's inversion point, tested via in-process duplex echo + failing dials). - `register_tunnel_openable` takes `dial` + `identity_witness` (the CF-006 probe seam) — the POC's registration closure had no dial injection. - `ResourceRegistry` keys on `(String, Substrate)` (the enum, not a &'static str — `Hash` added to `Substrate`). - `TargetHandle` carries `+ Sync` halves (the F-1 plan-payload bound). - Harness (tests/harness.rs): the reverse POC's topology (producer = `from_connection_with_serving` on the connect side, consumer = ChannelsAdapter + capturing install hook) with the dial closure standing in for sockets. The consumer drives the open op via `call_with_payload` on channel 0 and adopts the producer-allocated ID. - 11 integration tests: round-trip through `pump_bidi` (wrapper reaps the producer channel on pump completion — R-02), unknown_resource + dial_failed typed errors (no phantom channel either side), FORBIDDEN identity-less (fails closed), transport-identity open (CF-005 (b)), token precedence (CF-005 (a) + the CF-006 witness), same-resource concurrency (no plan race), late registration (W2), unknown substrate schema rejection (INVALID_INPUT — the registry's schema-validation runs before the establisher), outbound-calls- resolve-while-serving (ADR-022 §2), substrate-keyed registry lookups. - alkcall 0.7.0 note: the establisher's per-call `auth` carries the dispatch-resolved identity (CF-005 corollary) — the witness proves the overlay; `INVALID_INPUT` is the wire code for a schema failure (distinct from `channel:open_failed` establishment failures). - `futures` crate used for `BoxFuture` in `DialFn` (already a dep). ## Summary Producer half complete: dial-shape establisher (registry lookup → injected dial → `Establishment::new(plan)`), the substrate-agnostic pump handler (`pump_bidi` awaited inline, R-02), and `register_tunnel_openable` (spec + establisher + pump + dial injection + witness). 14 unit tests (params + codec) + 11 integration tests over the duplex harness pass. Verified: cargo test, clippy --all-targets -D warnings (native + wasm32), fmt --check, wasm32 check — all clean. The listen establisher variant lands with `tunnels/producer-listen`.