- 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
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 |
|
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:
src/door.rsadditions (featurelocal), 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 (
FrontDooradditions or a siblingUdpFrontDoor; decide at implementation against the landeddoor.rsshape and record in Notes; the spec leaves the exact constructor shape open).
- Bind a real UDP socket (the relay socket, the only place a
real address exists; the vanilla client
- Tests (
tests/door-udp.rs): the POC #2 test-8 shape end to end: a real UDP echo target, the producer withlocal::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 testpasses;cargo test --all-featurespassescargo clippy --all-targets -- -D warningsclean;cargo fmt --checkclean- 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
UdpFrontDoorwith its own builder (bind+idle), notFrontDooradditions: 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
AssociateSessionbehind 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 incrementalDatagramReaderdecode). The two pumps join viaselect!(whichever ends first closes the door); the pump is inlined inserve_onbecause the split halves borrow the session (the borrow must end before the teardown'sshutdown). The read half's decode carries a fresh per-callDatagramReader, 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 ownrecv_datagramcontract (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
UndecodableFrameexit, which the door never triggers by construction). - The idle bound covers the socket→channel pump's
recv_fromonly (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.