@@ -163,6 +163,8 @@ function makeHarness(
163163 sendDelayMs ?: number
164164 onSendStart ?: ( activeSends : number ) => void
165165 waitForDeliveryNeverSettles ?: boolean
166+ waitForInjectedNeverSettles ?: boolean
167+ failInjected ?: boolean
166168 } = { }
167169) : {
168170 bridge : IntegrationEventBridge
@@ -171,6 +173,7 @@ function makeHarness(
171173 sent : SentMessage [ ]
172174 listAgentsCalls : string [ ]
173175 deliveryConfirmationCalls : SentMessage [ ]
176+ injectedConfirmationCalls : SentMessage [ ]
174177 unsubscribedCount : ( ) => number
175178 emit ( event : ChangeEvent ) : Promise < void >
176179} {
@@ -179,6 +182,7 @@ function makeHarness(
179182 const sent : SentMessage [ ] = [ ]
180183 const listAgentsCalls : string [ ] = [ ]
181184 const deliveryConfirmationCalls : SentMessage [ ] = [ ]
185+ const injectedConfirmationCalls : SentMessage [ ] = [ ]
182186 const subscriptions : Subscription [ ] = [ ]
183187 let unsubscribedCount = 0
184188 let activeSends = 0
@@ -235,7 +239,26 @@ function makeHarness(
235239 deliveryConfirmationCalls . push ( { projectId, input } )
236240 await new Promise ( ( ) => undefined )
237241 }
238- : undefined
242+ : undefined ,
243+ sendMessageAndWaitForInjected : async ( projectId , input ) => {
244+ injectedConfirmationCalls . push ( { projectId, input } )
245+ activeSends += 1
246+ options . onSendStart ?.( activeSends )
247+ try {
248+ if ( options . sendDelayMs ) {
249+ await new Promise ( ( resolve ) => setTimeout ( resolve , options . sendDelayMs ) )
250+ }
251+ if ( options . failSend ) throw new Error ( 'broker unavailable' )
252+ sent . push ( { projectId, input } )
253+ if ( options . failInjected ) throw new Error ( 'delivery injection timed out' )
254+ if ( options . waitForInjectedNeverSettles ) {
255+ await new Promise ( ( ) => undefined )
256+ }
257+ return { eventId : `evt-${ injectedConfirmationCalls . length } ` , targets : [ input . to ] }
258+ } finally {
259+ activeSends -= 1
260+ }
261+ }
239262 }
240263 } )
241264
@@ -252,6 +275,7 @@ function makeHarness(
252275 sent,
253276 listAgentsCalls,
254277 deliveryConfirmationCalls,
278+ injectedConfirmationCalls,
255279 unsubscribedCount : ( ) => unsubscribedCount ,
256280 emit
257281 }
@@ -846,6 +870,89 @@ test('slack edits after a blind alias claim still inject once the content change
846870 assert . match ( harness . sent [ 1 ] . input . text , / M e s s a g e : \n e d i t e d S l a c k m e s s a g e / u)
847871} )
848872
873+ test ( 'slack unchanged-content replay re-drives after injected delivery is not confirmed' , async ( ) => {
874+ const options = { failInjected : true }
875+ const harness = makeHarness ( [ 'alice' ] , options )
876+ const warnCalls : unknown [ ] [ ] = [ ]
877+ const originalWarn = console . warn
878+ console . warn = ( ...args : unknown [ ] ) => {
879+ warnCalls . push ( args )
880+ }
881+
882+ try {
883+ await withMockedNow ( '2026-06-05T14:00:00.000Z' , async ( ) => {
884+ await harness . bridge . reconcile ( 'project-1' , [
885+ integration ( {
886+ provider : 'slack' ,
887+ integrationId : 'slack-1' ,
888+ mountPaths : [ '/slack/channels/C123ABC__proj-cloud' ] ,
889+ scope : { notifyAgents : [ 'alice' ] }
890+ } )
891+ ] )
892+ } )
893+
894+ const path = '/slack/channels/C123ABC__proj-cloud/messages/1780668000_000000/meta.json'
895+ await harness . emit ( changeEvent ( path , 'slack' ) )
896+ await waitForSent ( harness , 1 )
897+ await waitUntil ( ( ) => warnCalls . some ( ( call ) => call [ 0 ] === '[integration-events] delivery injected confirmation failed' ) )
898+
899+ options . failInjected = false
900+ await harness . emit ( changeEvent ( path , 'slack' ) )
901+ await waitForSent ( harness , 2 )
902+ } finally {
903+ console . warn = originalWarn
904+ }
905+
906+ assert . deepEqual ( harness . sent . map ( ( message ) => message . input . to ) , [ 'alice' , 'alice' ] )
907+ } )
908+
909+ test ( 'slack unchanged-content replay is suppressed after injected delivery commits' , async ( ) => {
910+ const harness = makeHarness ( [ 'alice' ] )
911+
912+ await withMockedNow ( '2026-06-05T14:00:00.000Z' , async ( ) => {
913+ await harness . bridge . reconcile ( 'project-1' , [
914+ integration ( {
915+ provider : 'slack' ,
916+ integrationId : 'slack-1' ,
917+ mountPaths : [ '/slack/channels/C123ABC__proj-cloud' ] ,
918+ scope : { notifyAgents : [ 'alice' ] }
919+ } )
920+ ] )
921+ } )
922+
923+ const path = '/slack/channels/C123ABC__proj-cloud/messages/1780668000_000000/meta.json'
924+ await harness . emit ( changeEvent ( path , 'slack' ) )
925+ await waitForSent ( harness , 1 )
926+ await harness . emit ( changeEvent ( path , 'slack' ) )
927+ await waitForDropped ( 'project-1' , 1 )
928+
929+ assert . equal ( harness . sent . length , 1 )
930+ } )
931+
932+ test ( 'slack channel targets do not pin unresolved injected-delivery claims' , async ( ) => {
933+ const harness = makeHarness ( [ 'alice' ] )
934+
935+ await withMockedNow ( '2026-06-05T14:00:00.000Z' , async ( ) => {
936+ await harness . bridge . reconcile ( 'project-1' , [
937+ integration ( {
938+ provider : 'slack' ,
939+ integrationId : 'slack-1' ,
940+ mountPaths : [ '/slack/channels/C123ABC__proj-cloud' ] ,
941+ scope : { notifyChannels : [ '#triage' ] }
942+ } )
943+ ] )
944+ } )
945+
946+ const path = '/slack/channels/C123ABC__proj-cloud/messages/1780668000_000000/meta.json'
947+ await harness . emit ( changeEvent ( path , 'slack' ) )
948+ await waitForSent ( harness , 1 )
949+ await harness . emit ( changeEvent ( path , 'slack' ) )
950+ await waitForSent ( harness , 2 )
951+
952+ assert . deepEqual ( harness . sent . map ( ( message ) => message . input . to ) , [ '#triage' , '#triage' ] )
953+ assert . equal ( harness . injectedConfirmationCalls . length , 0 )
954+ } )
955+
849956test ( 'remote replayed events older than the subscription session are dropped by default' , async ( ) => {
850957 const harness = makeHarness ( )
851958
@@ -1804,8 +1911,8 @@ test('integration event delivery failures use aggregated warn cadence by default
18041911
18051912 assert . equal ( debugCalls . length , 0 )
18061913 assert . equal ( warnCalls . length , 2 )
1807- assert . equal ( warnCalls [ 0 ] [ 0 ] , '[integration-events] event delivery failed' )
1808- assert . equal ( warnCalls [ 1 ] [ 0 ] , '[integration-events] event delivery failed' )
1914+ assert . equal ( warnCalls [ 0 ] [ 0 ] , '[integration-events] delivery injected confirmation failed' )
1915+ assert . equal ( warnCalls [ 1 ] [ 0 ] , '[integration-events] delivery injected confirmation failed' )
18091916 assert . deepEqual ( warnCalls . map ( ( call ) => ( call [ 1 ] as { occurrences : number } ) . occurrences ) , [ 1 , 26 ] )
18101917 assert . deepEqual (
18111918 warnCalls . map ( ( call ) => ( call [ 1 ] as { suppressedSinceLastLog : number } ) . suppressedSinceLastLog ) ,
@@ -1816,10 +1923,10 @@ test('integration event delivery failures use aggregated warn cadence by default
18161923 assert . equal ( telemetry . brokerSendsDeferred >= 0 , true )
18171924 assert . deepEqual ( { ...telemetry , brokerSendsDeferred : 0 } , {
18181925 eventsReceived : 26 ,
1819- eventsInjected : 0 ,
1926+ eventsInjected : 26 ,
18201927 eventsCoalesced : 0 ,
18211928 eventsDropped : 0 ,
1822- brokerSends : 0 ,
1929+ brokerSends : 26 ,
18231930 brokerSendsDeferred : 0 ,
18241931 queueDepth : 0 ,
18251932 mountCount : 0 ,
@@ -1850,7 +1957,7 @@ test('failed deliveries release the dedupe key so duplicate events retry', async
18501957
18511958 const path = '/slack/channels/C123ABC__proj-cloud/messages/1780668000_000000/meta.json'
18521959 await harness . emit ( changeEvent ( path , 'slack' ) )
1853- await waitUntil ( ( ) => warnCalls . some ( ( call ) => call [ 0 ] === '[integration-events] event delivery failed' ) )
1960+ await waitUntil ( ( ) => warnCalls . some ( ( call ) => call [ 0 ] === '[integration-events] delivery injected confirmation failed' ) )
18541961 assert . equal ( harness . sent . length , 0 )
18551962
18561963 // The same logical change arrives again (remote copy of a local mount
@@ -2053,16 +2160,10 @@ test('integration event dispatcher coalesces rapid distinct revisions for the sa
20532160 } )
20542161} )
20552162
2056- test ( 'integration event fanout sends to recipients sequentially' , async ( ) => {
2057- let maxActiveSends = 0
2163+ test ( 'integration event fanout sends to recipients in stable order' , async ( ) => {
20582164 const harness = makeHarness (
20592165 Array . from ( { length : 12 } , ( _ , index ) => `agent-${ index } ` ) ,
2060- {
2061- sendDelayMs : 2 ,
2062- onSendStart : ( activeSends ) => {
2063- maxActiveSends = Math . max ( maxActiveSends , activeSends )
2064- }
2065- }
2166+ { sendDelayMs : 2 }
20662167 )
20672168
20682169 await harness . bridge . reconcile ( 'project-1' , [
@@ -2076,7 +2177,6 @@ test('integration event fanout sends to recipients sequentially', async () => {
20762177 await harness . emit ( changeEvent ( '/linear/issues/AR-1.json' , 'linear' ) )
20772178 await waitUntil ( ( ) => harness . sent . length === 12 )
20782179
2079- assert . equal ( maxActiveSends , 1 )
20802180 assert . deepEqual ( harness . sent . map ( ( message ) => message . input . to ) , [
20812181 'agent-0' ,
20822182 'agent-1' ,
0 commit comments