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:
@@ -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.
|
||||
Reference in New Issue
Block a user