feat(compass): durable CommitConversationFrame sink-ack handler - #43
Open
seal-agent wants to merge 2 commits into
Open
feat(compass): durable CommitConversationFrame sink-ack handler#43seal-agent wants to merge 2 commits into
seal-agent wants to merge 2 commits into
Conversation
Implement RunnerService.CommitConversationFrame, the durable at-most-once counterpart to the loss-tolerant PublishEvents/Deliver path (#24 / OQ-3). The handler resolves the relayed session_id to its bound agent account (fail-closed, mirroring RelayCommsCall: unbound -> CodeNotFound, never a stale account or bootstrap admin), then commits one agent-authored conversation frame at most once keyed on the agent-minted idempotency_key. The key threads to the store's client_request_id, so a retried frame hits AppendMessage's ON CONFLICT (author_account_id, client_request_id) path, writes no second row, does not re-fan MessagePosted, and returns the original row's id -- committed=true on both a fresh commit and a replay. - comms: CommitAgentPostKeyed threads idempotency_key -> ClientRequestId; CommitAgentUpdateKeyed is a documented pass-through (an UPDATE is idempotent by replacement, addressed by message.id, so no dedup key is needed and no store-write signature changes). Both added beside the untouched unkeyed methods the Deliver-path sink still drives. - runnerhub: Hub.CommitConversationFrame (nil-comms -> CodeUnavailable; unbound session -> CodeNotFound) delegates to commitFrame, a oneof dispatcher (posted -> keyed post, updated -> keyed update, neither -> CodeInvalidArgument). Comms errors propagate as-is (already Connect-coded via edgeError) so the Runner's retryable/terminal split reads the right code; a non-commit is never committed=false with a nil error. - handler: (*Handler).CommitConversationFrame with the runnerSubjectFrom guard, fully implementing the generated interface. - seq shipped as 0 (deferred): the downstream consumer reads neither message_id nor seq today; the proto doc-comment carries the caveat. Tests: 5 hub unit tests (fail-closed NotFound, nil-comms Unavailable, neither-variant InvalidArgument, keyed-attribution happy path, error propagation) and 4 comms pgtests including a deterministic at-most-once replay proof (row count stays 1, original id, store head does not advance). Refs SEA-1364 Co-Authored-By: seal <noreply@sealedsecurity.com>
Two review-loop test additions on the durable sink-ack handler (PR #43), both defending an invariant the first-round suite left to inference: - runnerhub/commit_frame_test.go: a hub UPDATE-arm test symmetric to the posted happy path. The ConversationUpdated dispatch arm was proven only at the comms layer + via the fake recorder, never through Hub.commitFrame itself; this asserts the arm forwards under the bound account with the key threaded and returns the UPDATE's own row id (m-99) — a mis-wire that returned the posted id or the wrong frame now fails here (verified red). - runnerhub/auth_test.go: TestAccountTokenRejectedOnEveryRPCPath gains a commit_conversation_frame subtest, replacing two dead duplicate relay_comms_call slots (Go suffixed #1/#2, so they ran but covered nothing new). The new RPC's door-interceptor wiring is now asserted Unauthenticated for an account token, closing the door-coverage gap. Refs SEA-1364 Co-Authored-By: seal <noreply@sealedsecurity.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements
RunnerService.CommitConversationFrame— the durable, at-most-once counterpart to the loss-tolerantPublishEvents/Deliverconversation path (SEA-1364 T3 substrate landed in #32; sink-ack RPC landed in #42). This is the handler that gives the Runner a synchronous, idempotent commit for agent-authored conversation frames.The handler resolves the relayed
session_idto its bound agent account (fail-closed, mirroringRelayCommsCall: unbound →CodeNotFound, never a stale account or bootstrap admin), then commits one agent-authored conversation frame at most once, keyed on the agent-mintedidempotency_key. The key threads to the store'sclient_request_id, so a retried frame hitsAppendMessage'sON CONFLICT (author_account_id, client_request_id)path: no second row, no re-fan ofMessagePosted, and the original row's id is returned —committed=trueon both a fresh commit and a replay.Layers
agent_caller.go):CommitAgentPostKeyedthreadsidempotency_key→ClientRequestId;CommitAgentUpdateKeyedis a documented pass-through (an UPDATE is idempotent by replacement, addressed bymessage.id, so no dedup key is needed and no store-write signature changes). Both added beside the untouched unkeyed methods the Deliver-path sink still drives.hub.go,relay_comms.go):Hub.CommitConversationFrame(nil-comms →CodeUnavailable; unbound session →CodeNotFound) delegates tocommitFrame, a oneof dispatcher (posted→ keyed post,updated→ keyed update, neither →CodeInvalidArgument). Comms errors propagate as-is (already Connect-coded viaedgeError) so the Runner's retryable/terminal split reads the right code; a non-commit is nevercommitted=falsewith a nil error.handler.go):(*Handler).CommitConversationFramewith therunnerSubjectFromguard, fully implementing the generated interface.Contract notes
committed:trueon a fresh commit AND on an idempotent replay. A non-commit is NEVERcommitted=false+ nil-err — always a Connect status error. The Runner drives at-least-once offerr==nil ⇒ committed.message_id: real and replay-stable (the original row id, re-read viaAppendMessage's ON CONFLICT path,inserted=false, no re-fan).seq: shipped as0(deferred) — the downstream consumer reads neithermessage_idnorseqtoday; the proto doc-comment carries the caveat.Tests
NotFound, nil-commsUnavailable, neither-variantInvalidArgument, keyed-attribution happy path (asserts exactly-1 call, correct account attribution, exact frame forwarded, key threaded,committed/message_id/seq), and comms-error code propagation.ListMessagescount stays 1, and store head does not advance (⇒ no re-fan).Refs SEA-1364