feat: producer-listen — listen establisher (shape 2) + AcceptQueue contract

- AcceptFn: the injected accepted-connection source (the assembly
  layer owns the listener + accept loop; the protocol never binds —
  OQ-TN-04).
- AcceptQueue: the protocol-side queue contract (async push/pop/
  close; FIFO always-before-take ordering; close-while-waiting
  resolves None). Empty-queue posture is assembly-owned (a late
  accept is legitimate; resource_shortage is the closure's mapping;
  the wrapper's 10s establishment timeout is the backstop).
- listen_establisher: same open op, same params, same typed errors —
  registry namespace gate → accept() → Establishment::new(plan);
  the pump handler is untouched (plan-flow with a different source).
- register_tunnel_listen_openable: same spec/pump registration with
  the listen establisher (the honest shape vs a dial/accept enum:
  the establisher is the only difference; one establisher per op id
  per session registry — documented).
- Tests (tests/producer_listen.rs, 6): listen flow end-to-end, FIFO
  ordering across two opens (R-01 plan-flow, listen-flavored), empty
  queue -> resource_shortage, closed listener -> dial_failed,
  unknown resource -> unknown_resource, late-push wait-then-resolve.
- Harness: RegistrationMode enum + wire_listen.
- TargetHandle gains a structural Debug impl (test ergonomics).

Verified: cargo test green (44), clippy -D warnings (native + wasm32),
fmt clean, wasm32 check passes.
This commit is contained in:
2026-09-08 09:37:22 +00:00
parent fb2389bd62
commit 09d32d5aa6
6 changed files with 589 additions and 15 deletions
+55 -2
View File
@@ -1,7 +1,7 @@
---
id: tunnels/producer-listen
name: Listen establisher — the producer-side listener variant
status: pending
status: completed
depends_on: [tunnels/producer-open-op]
scope: narrow
risk: medium
@@ -87,6 +87,59 @@ queue → `resource_shortage`, closed listener → `dial_failed`.
> Agent fills during implementation.
- **Registration shape (the "pick the honest shape" decision):** a
separate `register_tunnel_listen_openable(core, registry, on_registry,
auth, accept: AcceptFn)` — not a dial/accept enum on
`register_tunnel_openable`. Rationale: the establisher is the only
difference; `register_openable_with_establisher` takes a pre-built
`OpenEstablisher`, so both variants share the same spec + pump
handler + registration call, and a session serves ONE establisher
per op id (a dual-shape producer registers on separate per-session
registries). An enum would have added a mode flag the wrapper does
not need.
- **`AcceptQueue::push` is async** (lock-acquiring): the accept loop's
step is async anyway; a sync `push` would need a sync mutex with
cross-thread notify — not worth it for a queue that drains one
handle per open.
- **Empty-queue posture is assembly-owned, not queue-owned:** an
empty queue during establishment is NOT inherently an error (a late
accept is legitimate — `accept_wait_resolves_when_push_arrives_late`
pins the wait-then-resolve shape). The mapping to
`resource_shortage` belongs to the accept closure (the assembly
knows its listener's bound: drained listener, accept budget); the
wrapper's establishment timeout (10s) is the backstop. The task
sketch's "empty queue → resource_shortage" is realized by the
assembly mapping — the test pins the fail-fast closure posture.
- **`closed_listener → dial_failed`** is likewise the closure's
mapping (the queue's `close()` + pop → `None` → the closure maps).
The typed-error table holds: both reasons verified on the wire via
`channel:open_failed` details.
- **AcceptFn = the queue pop wrapped in a closure** in the tests; the
`local`-gated listener helper (local-socket-halves) feeds real
accepted sockets through the same queue.
## Summary
> Agent fills this on completion.
> Agent fills this on completion.
Implemented the listen establisher (shape 2) per producer.md §The
Establisher: `AcceptFn` (the injected accepted-connection source),
`AcceptQueue` (the protocol-side queue contract: async push/pop/
close, FIFO with always-before-take ordering, close-while-waiting
resolves None), `listen_establisher` (registry namespace gate →
accept() → `Establishment::new(plan)` — the same plan flow, a
different halves source; the pump handler is untouched), and
`register_tunnel_listen_openable` (the same spec/pump registration,
listen establisher).
Tests: `tests/producer_listen.rs` (6) — the listen flow end-to-end
(pre-accepted handle round-trips through the pump), FIFO ordering
across two opens (the R-01 plan-flow property, listen-flavored),
empty queue → `resource_shortage` (fail-fast closure posture pinned),
closed listener → `dial_failed`, unknown resource →
`unknown_resource`, and the late-push wait-then-resolve shape.
Harness: `RegistrationMode` enum + `wire_listen` on the existing
topology.
Verified: cargo test green (44 total), clippy `-D warnings` clean
(native + wasm32), fmt clean, wasm32 check passes.