--- id: implementation/core-consumer-associate name: "Core — consumer ASSOCIATE: AssociateSession + e2e tests" status: pending depends_on: [implementation/core-consumer-connect, implementation/core-producer-associate] scope: moderate risk: medium impact: component level: implementation --- ## Description Implement the UDP ASSOCIATE consumer session in `src/consumer.rs` per consumer.md §The associate session (POC #2's shape): the typed client that opens the channel, receives the sentinel, and speaks the framed datagram stage. Deliverables (port from `/workspace/alksocks-udp-poc/src/consumer.rs`, adapted to the crate's wire codec): 1. **`AssociateSession::associate(client)`**: opens the channel, negotiates (NoAuth), sends ASSOCIATE, reads the reply — **receives the sentinel BND.ADDR and never uses it for addressing** (the handle is the channel; ADR-003). 2. **`send_datagram(dst, bytes)`**: frames once (`[len: u16 BE][addr + data]` via the crate's wire codec — the framing-once mirror) and writes the frame. 3. **`recv_datagram()`** returns `(source_addr, bytes)` per datagram — **the incremental reader state (mid-frame position + batched-datagram pending queue) persists across calls**: a datagram split across channel chunks survives a caller-driven receive loop (POC #2's finding — the first cut dropped the reader state per call; the 2-byte-prefix chunk test exposed it). Write the persistence test (the POC's chunk-splitting echo shape). 4. **`shutdown()`** half-closes the stream — the mux's implicit-EOF sentinel reaches the producer's stage as EOF and the association ends (association lifetime = channel lifetime). 5. **Teardown ownership**: the session owns the channel; on Drop it closes/joins (alktunnels ADR-005 posture). No `Clone` (compile-asserted). 6. **Tests** (end-to-end through the harness): associate → send/recv round-trips (multiple in-order datagrams, one association); the split-datagram persistence test; empty-payload datagram round-trip; shutdown → the producer's stage ends and a fresh associate succeeds immediately (the POC's test 4, from the consumer side); Drop teardown (drop mid-stream → the producer's stage sees EOF, the connection stays healthy); CONNECT + ASSOCIATE on the same registration (the regression guard extends). ## Acceptance Criteria - [ ] `AssociateSession` with `associate`/`send_datagram`/ `recv_datagram`/`shutdown` per the POC shape - [ ] Reader state persists across `recv_datagram` calls (the split-datagram test) - [ ] Framing-once mirror (send frames exactly once; recv decodes incrementally) - [ ] Teardown ownership on Drop; no `Clone` (compile-asserted) - [ ] E2E tests through the harness (round-trips, shutdown, Drop teardown, regression guard) - [ ] `cargo test` green; clippy/fmt clean; both wasm checks clean ## References - docs/architecture/consumer.md §The associate session - docs/architecture/decisions/003-udp-handle-swap.md (the consumer mirror invariants) - POC source: `/workspace/alksocks-udp-poc/src/consumer.rs` (`UdpAssociation` — the reader-state finding's origin) ## Notes > To be filled by implementation agent ## Summary > To be filled on completion