agent: contact request rejection and service requests#1833
agent: contact request rejection and service requests#1833epoberezkin wants to merge 14 commits into
Conversation
There was a problem hiding this comment.
Adds two agent capabilities on top of the DR-enabled contact address feature: (1) contact-request rejection with an optional reason delivered over the double ratchet (RJCT), and (2) a service RPC round-trip — sendServiceRequest blocks for a single AgentServiceResponse/AgentRejection reply, with sync and async variants on both sides.
I read every touched file in full, followed the message flow through Agent.hs (serviceRequest_, prepareReply, sendReply{Sync,Async}, joinConnSrv', smpConfirmation, smpContactRequest, ICReplyDel, cleanupManager), the store/schema/migration changes, and the protocol encodings. The design is coherent and the tests exercise sync, async, rejection, and cross-outage resilience. Encodings don't collide (AgentMessage A/P/J, AgentMessageType A/P/J vs existing tags), smpConfirmation is always followed by ack, the in-memory serviceRequests TMVar map is keyed by a freshly-generated connId so concurrent requests can't collide, and the orElse (registerDelay) timeout logic is correct. Leftover requester connections and unanswered received requests are cleaned up by getExpiredServiceConns/deleteExpiredServiceRequests. No blocking issues found.
Non-blocking observations for the author:
-
Request vs response timeout asymmetry. The requester's end-to-end wait is
serviceRequestTimeout(30s) while the responder may still reply withinserviceResponseTimeout(180s) —prepareReplyaccepts requests up to 180s old andAM_SRV_RESPdelivery is retried for 180s. A reply produced between 30s and 180s arrives after the requester has already timed out and torn down the reply connection, yieldingERR (A_SERVICE ASENoPendingRequest)and a wasted round-trip. If the 30s/180s split is intentional (interactive requester patience vs. service-side lifetime), fine; otherwise the requester wait should be>=the response window. Confirm this is deliberate. -
Non-DR contact address silently degrades to a contact invitation.
sendServiceRequest'→joinConnSrv'CRContactUri: when the target address publishes no ratchet keys (ratchet_ == Nothing), themkInner/AgentServiceRequestpayload is ignored and a plainAgentInvitationis sent. The recipient then sees a normalREQ(notSREQ) and the requester blocks until the 30s timeout. Service RPC only makes sense against a DR address, so consider failing fast with a clear error in that branch rather than emitting a contact invitation and timing out. -
Minor duplication. The
ICReplyDelhandler body (secure snd queue →submitPendingMsg→deleteConnectionAsync' True) is identical tosendReplySyncwithnm = NRMBackground; could callsendReplySyncdirectly. -
Migration rename.
M20260712_address_dr→M20260712_address_dr_rpc(name and column additions) is safe only because theaddress_drmigration is unreleased; renaming an applied migration would break the migrations table. Assuming this branch supersedes the still-unreleased address-dr work, this is fine.
c59629a to
98caf57
Compare
There was a problem hiding this comment.
Reviewed the full branch (d5f48de3b56b...ccc1d7f8eca5): all 18 files, following the new agent APIs, the store/migration changes, and the SMP transmission handling.
The change adds two capabilities over contact-address double-ratchet connections:
- Contact request rejection with a reason (
rejectContact/rejectContactAsync), surfaced to the requester as the newRJCTevent. - Service (RPC) requests (
sendServiceRequest/Async), with responses (sendServiceReply/Async) and rejections (rejectServiceRequest/Async), surfaced to the service as the newSREQevent.
The wire changes are consistent: AgentInvitationDR→AgentContactRequest (envelope tag 'J'→'A'), three new AgentMessage variants (A/P/J) with matching AgentMessageTypes, and the new AEvent/AEventTag constructors are threaded through aEventTag. Roundtrip encoding is covered by the new ConnectionRequestTests case, and the async/resilient flows have functional tests. The serviceRequests TMVar map, the timeout handling (serviceRequestTimeout/serviceResponseTimeout), the ICReplyDel deferred-reply command, and the cleanup path (getExpiredServiceConns/deleteExpiredServiceRequests) all hang together correctly, including the orphaned-connection GC after an agent restart.
Concurrency and lifecycle look sound: the TMVar is inserted before the send is enqueued, the blocking caller uses registerDelay+orElse for timeout, and stale/cancelled requests are handled by the Nothing branch in the async JOIN JRServiceReq worker. The serviceResponse connection flag and service_request invitation flag are persisted with sensible migration defaults (created_at defaulting to epoch for pre-existing rows, which never match the service_* GC predicates).
Only one nit found — a typo in a new record field name (conntactReq), suggested inline.
Two non-blocking observations:
- Migration rename.
M20260712_address_dris renamed toM20260712_address_dr_rpc(folding in the new columns) rather than adding a new migration. This is correct only if20260712_address_drhas never shipped to any client DB; if it has, deployed installs recorded the old name and would re-run theCREATE TABLE address_ratchet_keys. Given both migrations share the same date and author and this is a stacked branch, it appears to be in-development — worth a quick confirm. - Payload size. Service request/response payloads are encrypted into the fixed
e2eEncConnInfoLengthenvelope (~11KB with PQ, ~14.8KB otherwise). Payloads beyond that fail encryption and surface as an error to the caller. Fine as a design bound for RPC, just noting it.
No description provided.