Files
alksocks/tasks/implementation/door-udp.md
T
glm-5.3-flash e196ed43e3 docs: remove em dashes from repo prose and forbid them going forward
- Rewrote em dash prose across the whole tree outside docs/reviews/
  (the historical reviews stay verbatim, matching the alktunnels and
  alktty precedent): src doc comments and inline comments (587),
  docs/architecture (319), docs/research + sdd_process (484), tests
  (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77),
  AGENTS.md (85), and the .opencode/agents prompts (109)
- Fenced content follows the precedent: comments, ASCII-diagram label
  separators, and template placeholders rewritten; wire data, JSON/BAST
  documents, byte diagrams, and transcripts survive verbatim; assert,
  expect, panic, error-display, and format message literals rewritten
  as prose; no code, fixture, or test data change
- src/rfc1928/LICENSE: the two dashes in alksocks' own provenance
  header rewritten; the MIT legal text untouched
- AGENTS.md adds Project Convention 18: no em dashes in prose; varied
  restructure, no spaced-hyphen substitutes, fenced prose rule, en
  dash numeric ranges exempt, docs/reviews/ exempt from the greps
- tasks/*.md frontmatter: the name values rewritten by the sweep are
  now quoted (a raw colon inside an unquoted YAML scalar parsed as
  null); taskgraph validate reports 37 tasks, no issues

Verification: cargo test; cargo clippy --all-targets -D warnings;
cargo fmt --check; cargo doc --no-deps; fuzz corpus replay
(cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/ and the bast.md BAST
JSON data fence
2026-11-03 19:31:53 +00:00

7.2 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
implementation/door-udp Front doors: the UDP door (the RFC codec boundary) completed
implementation/door-tcp
narrow medium component implementation

Description

The consumer's UDP front door (feature local): a real UDP socket exposing an ASSOCIATE association to vanilla clients, the POC #2 test-8 shape. The RFC UDP-header codec lives ONLY at this boundary (ADR-003); the in-band wire carries {addr, data} frames.

Deliverables:

  1. src/door.rs additions (feature local), the UDP door:
    • Bind a real UDP socket (the relay socket, the only place a real address exists; the vanilla client sendto()s it).
    • Open the association in-band via AssociateSession::associate (the reply's sentinel BND.ADDR is received and never used, as the handle is the channel, ADR-003).
    • The datagram pump (two pumps, joined, per the ADR-050 two-pump contract): socket→channel decodes the RFC header (parse_udp_request), and FRAG ≠ 0 → refuse (drops the datagram; the v1 posture, since fragmentation cannot be expressed in-band), sends {addr, data} in-band (send_datagram); channel→socket encodes the reply header (new_udp_header) and sends the RFC-header'd datagram back to the client's source address. Zero payload translation.
    • Association lifetime = channel lifetime (ADR-003): the door's association ends when the channel ends; the door's UDP socket closes with it. A per-client idle bound (no datagrams within a window → close the association) is the door-side defense (ADR-014), constructor config, mirroring the TCP door's knobs.
    • The door's bind address is constructor config (consumer.md: "the UDP door's bind address is assembly-layer configuration").
    • Surface shape: follow the TCP door's builder (FrontDoor additions or a sibling UdpFrontDoor; decide at implementation against the landed door.rs shape and record in Notes; the spec leaves the exact constructor shape open).
  2. Tests (tests/door-udp.rs): the POC #2 test-8 shape end to end: a real UDP echo target, the producer with local::udp_ egress(), the door, a vanilla RFC-header'd datagram in → the echo back RFC-header'd (the vanilla client simulated at the RFC boundary); FRAG ≠ 0 refused (dropped, association stays healthy); the empty-datagram invariant through the door (a zero-length payload round-trips, never EOF, F-2); the idle bound closes an idle association; a fresh association succeeds after teardown.

Acceptance Criteria

  • cargo test passes; cargo test --all-features passes
  • cargo clippy --all-targets -- -D warnings clean; cargo fmt --check clean
  • Default wasm checks stay green (door code is local-gated)
  • The RFC UDP-header codec appears only at the door boundary (in-band stays {addr, data} frames), pinned by test
  • FRAG ≠ 0 refused at the door, pinned by test
  • The empty datagram (zero-length payload) round-trips through the door, never EOF, pinned by test (the F-2 invariant at the vanilla boundary)
  • Association lifetime = channel lifetime; teardown leaves the connection healthy, pinned by test
  • The idle bound is constructor config, pinned by test

References

  • docs/architecture/decisions/003-udp-handle-swap.md (the RFC codec at doors only), 014-idle-timeout-posture.md
  • docs/architecture/consumer.md §The Front Doors (the UDP door), §Wiring
  • POC reference: /workspace/alksocks-udp-poc/tests/udp_poc.rs (udp_front_door_vanilla_client_round_trip, the pump shape)
  • Landed surfaces: src/consumer.rs (AssociateSession, send_datagram/recv_datagram/split), src/rfc1928/mod.rs (new_udp_header/parse_udp_request), src/wire.rs (the frame codec)

Notes

  • Surface shape (the spec left the constructor open): a sibling UdpFrontDoor with its own builder (bind + idle), not FrontDoor additions: the UDP door shares no accept loop with the TCP door (UDP has no connections; one relay socket = one association), and the shared shape would have forced a mode split on every knob. serve(channel_client) + serve_on(socket, client) (the test/assembly seam), mirroring the TCP door.
  • The pump shape (an implementation finding worth recording): the POC #2 shape (both pumps sharing one AssociateSession behind a mutex) deadlocks in this landed surface: the channel read parks holding the lock while the socket side's send needs it. The door instead splits the association's stream (AssociateSession::split, the door bridge seam): one pump owns the in-band write half (socket→channel, framing via the wire codec), the other the read half (channel→socket, the incremental DatagramReader decode). The two pumps join via select! (whichever ends first closes the door); the pump is inlined in serve_on because the split halves borrow the session (the borrow must end before the teardown's shutdown). The read half's decode carries a fresh per-call DatagramReader, because the door serves one association per socket and the relay loop drains one frame per call; the mid-frame persistence case is the session's own recv_datagram contract (tested in consumer-associate).
  • The client's source address is learned from the first inbound datagram (std mutex, held never across an await) and reused for every reply, since the relay's shape is single-client. A reply before the first inbound datagram is parked (none exists in practice).
  • FRAG ≠ 0 drops the datagram and keeps the association (the v1 posture); a malformed RFC header also drops (garbage never goes in-band; the in-band refusal shape is the producer's UndecodableFrame exit, which the door never triggers by construction).
  • The idle bound covers the socket→channel pump's recv_from only (the client's silence is the defense target); the channel→socket pump has no window (its park IS the association's aliveness signal).

Summary

Landed the UDP front door (src/door.rs, feature local): UdpFrontDoor, a real relay UDP socket (constructor-config bind address) bridged onto one in-band association (AssociateSession::associate; the sentinel BND.ADDR received, never used). The RFC UDP-header codec appears only at the door boundary (parse_udp_request in / new_udp_header out); the in-band wire carries {addr, data} frames (framed once via the wire codec). FRAG ≠ 0 refused (dropped, association healthy); the empty-datagram invariant round-trips (F-2); association lifetime = channel lifetime (the door's socket closes with the pump; teardown reaps the channel); the client idle bound is constructor config and closes an idle association door-side. Two-pump shape with the split-halves correction recorded in Notes. Tests: tests/door-udp.rs (4 tests: the round-trip + empty datagram, FRAG refusal + health, idle bound + fresh-association health, fresh-association-after-teardown). Verification: cargo test green, cargo test --all-features green, clippy -D warnings clean (default + local + wasm target), cargo fmt --check clean, cargo doc --no-deps clean.