The raw document sink gets a reply handle, CultMeshRudpPutReply, with
accept() and refuse(reason). It can be answered now or later, from any
thread; dropping it unanswered refuses the put ("the put was not
answered"). A closure sink answers at once, and its server sends the
same datagrams as before (pinned by the byte test landed before this).
A put's transport acknowledgement is its answer (Q5-A): while any put a
session sent is unanswered, the server sends that session nothing new.
No end-of-poll ack, no Pong, no reply to a repeated Connect, no snapshot
response. Every packet built now carries ack fields covering every
received sequence, so each of these would acknowledge the put. Resends
of packets built earlier still go. One function, acknowledgement_withheld,
decides it.
Answers go through a channel and are handled at the start of each poll,
and again as a session's waiting messages are served. An accepted answer
that leaves the session with no unanswered put releases one ack. A
refusal ends the session with put-serve's fixed refusal and goodbye,
acknowledging nothing, and is returned as that poll's rejection. Acks
are cumulative, so a later put's answer never leaves before an earlier
one's.
A session's delivered messages are handled in order. A put is offered to
the sink at once, even behind a pending put, so pipelined puts can share
one write. A snapshot request waits for every earlier put's answer. A put
that arrives behind such a request is unanswered too, so the request's
response is built when its turn comes and held until that put is
answered.
Held puts are bounded by the existing session and byte limits (Q5-B).
Every session removal goes through remove_session, which forgets the
session's pending puts, so their answers are discarded and cannot reach
a later generation of the same address and connection id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>