@@ -58,6 +58,62 @@ func TestSessionAgentRun_QueueStripsOnComplete(t *testing.T) {
58 "RunComplete still correlates with the originating SendMessage")
58 "RunComplete still correlates with the originating SendMessage")
61+// TestDrainUncanceledQueue_FiltersUnderDispatchLock verifies that the
62+// queue drain evaluates the per-session cancel mark while holding the
63+// dispatch mutex (canceledBySeq's documented precondition). Queued calls
64+// at or below the cancel high-water mark are dropped, calls queued after
65+// the cancel (higher seq) survive, untracked enqueues (seq == 0) are
66+// dropped whenever any mark is present, and the queue is cleared.
67+func TestDrainUncanceledQueue_FiltersUnderDispatchLock(t *testing.T) {
71+ a := NewSessionAgent(SessionAgentOptions{
72+ Sessions: env.sessions,
73+ Messages: env.messages,
76+ const sessionID = "drain-session"
77+ a.messageQueue.Set(sessionID, []SessionAgentCall{
78+ {SessionID: sessionID, Prompt: "below", acceptSeq: 1},
79+ {SessionID: sessionID, Prompt: "at-mark", acceptSeq: 2},
80+ {SessionID: sessionID, Prompt: "after", acceptSeq: 3},
81+ {SessionID: sessionID, Prompt: "untracked", acceptSeq: 0},
83+ // Cancel high-water mark at seq 2: seq <= 2 and seq == 0 are covered.
84+ a.cancelMark.Set(sessionID, 2)
86+ survivors := a.drainUncanceledQueue(sessionID)
88+ require.Len(t, survivors, 1,
89+ "only the follow-up queued after the cancel (seq > mark) must survive")
90+ require.Equal(t, "after", survivors[0].Prompt)
92+ _, ok := a.messageQueue.Get(sessionID)
93+ require.False(t, ok, "drain must clear the session message queue")
96+// TestDrainUncanceledQueue_NoMarkKeepsAll verifies that with no cancel
97+// mark recorded, every queued call survives the drain.
98+func TestDrainUncanceledQueue_NoMarkKeepsAll(t *testing.T) {
102+ a := NewSessionAgent(SessionAgentOptions{
103+ Sessions: env.sessions,
104+ Messages: env.messages,
107+ const sessionID = "drain-nomark"
108+ a.messageQueue.Set(sessionID, []SessionAgentCall{
109+ {SessionID: sessionID, Prompt: "a", acceptSeq: 0},
110+ {SessionID: sessionID, Prompt: "b", acceptSeq: 5},
113+ survivors := a.drainUncanceledQueue(sessionID)
114+ require.Len(t, survivors, 2, "no cancel mark means all queued calls survive")
61// TestRunCompletePublisher_MustDeliverOverTakesPublish exercises the
117// TestRunCompletePublisher_MustDeliverOverTakesPublish exercises the
62// pubsub.Publisher interface change end-to-end: a Broker is the only
118// pubsub.Publisher interface change end-to-end: a Broker is the only
63// concrete Publisher implementation and must satisfy both Publish and
119// concrete Publisher implementation and must satisfy both Publish and