@@ -1624,6 +1624,9 @@ describe("iMessage monitor last-route updates", () => {
16241624 chat_identifier : "chat349" ,
16251625 chat_name : "Project group" ,
16261626 participants : [ "+15550001111" , "+15550002222" ] ,
1627+ sender : "+15550001111" ,
1628+ destination_caller_id : "+15550001111" ,
1629+ is_from_me : false ,
16271630 is_group : true ,
16281631 } ,
16291632 ] ,
@@ -1692,6 +1695,195 @@ describe("iMessage monitor last-route updates", () => {
16921695 expect ( dispatchParams ?. ctx . To ) . not . toBe ( "imessage:+15550001111" ) ;
16931696 } ) ;
16941697
1698+ it ( "repairs anchorless direct watch payloads so reply routing targets the authoritative remote peer (#104136)" , async ( ) => {
1699+ const issueGuid = "11111111-1111-4111-8111-111111111111" ;
1700+ const anchorlessNotification = {
1701+ id : 9500 ,
1702+ guid : issueGuid ,
1703+ chat_id : 0 ,
1704+ chat_guid : "" ,
1705+ chat_identifier : "" ,
1706+ sender : "+15550000001" ,
1707+ destination_caller_id : "+15550000001" ,
1708+ is_from_me : false ,
1709+ is_group : false ,
1710+ service : "iMessage" ,
1711+ text : "hello from broken anchor" ,
1712+ created_at : new Date ( ) . toISOString ( ) ,
1713+ } ;
1714+ const authoritativeHistory = {
1715+ id : 9500 ,
1716+ guid : issueGuid ,
1717+ chat_id : 42 ,
1718+ chat_guid : "iMessage;-;+15550000002" ,
1719+ chat_identifier : "+15550000002" ,
1720+ sender : "+15550000002" ,
1721+ destination_caller_id : "+15550000001" ,
1722+ is_from_me : false ,
1723+ is_group : false ,
1724+ service : "iMessage" ,
1725+ } ;
1726+
1727+ let onNotification : ( ( message : { method : string ; params : unknown } ) => void ) | undefined ;
1728+ const client = {
1729+ request : vi . fn ( async ( method : string , params ?: Record < string , unknown > ) => {
1730+ if ( method === "watch.subscribe" ) {
1731+ return { subscription : 1 } ;
1732+ }
1733+ if ( method === "chats.list" ) {
1734+ return { chats : [ { id : 42 } ] } ;
1735+ }
1736+ if ( method === "messages.history" ) {
1737+ expect ( params ?. chat_id ) . toBe ( 42 ) ;
1738+ return { messages : [ authoritativeHistory ] } ;
1739+ }
1740+ throw new Error ( `unexpected imsg method ${ method } ` ) ;
1741+ } ) ,
1742+ waitForClose : vi . fn ( async ( ) => {
1743+ onNotification ?.( {
1744+ method : "message" ,
1745+ params : { message : anchorlessNotification } ,
1746+ } ) ;
1747+ await Promise . resolve ( ) ;
1748+ await Promise . resolve ( ) ;
1749+ } ) ,
1750+ stop : vi . fn ( async ( ) => { } ) ,
1751+ } ;
1752+ createIMessageRpcClientMock . mockImplementation ( async ( params ) => {
1753+ if ( ! params ?. onNotification ) {
1754+ throw new Error ( "expected iMessage notification handler" ) ;
1755+ }
1756+ onNotification = params . onNotification ;
1757+ return client as never ;
1758+ } ) ;
1759+
1760+ await monitorIMessageProvider ( {
1761+ config : {
1762+ channels : {
1763+ imessage : {
1764+ dmPolicy : "allowlist" ,
1765+ allowFrom : [ "+15550000002" ] ,
1766+ } ,
1767+ } ,
1768+ messages : { inbound : { debounceMs : 0 } } ,
1769+ session : { mainKey : "main" } ,
1770+ } as never ,
1771+ runtime : { error : vi . fn ( ) , exit : vi . fn ( ) , log : vi . fn ( ) } ,
1772+ } ) ;
1773+
1774+ await vi . waitFor ( ( ) => {
1775+ expect ( dispatchInboundMessageMock ) . toHaveBeenCalledTimes ( 1 ) ;
1776+ } ) ;
1777+ const dispatchParams = dispatchInboundMessageMock . mock . calls . at ( 0 ) ?. [ 0 ] ;
1778+ expect ( dispatchParams ?. ctx . To ) . toBe ( "imessage:+15550000002" ) ;
1779+ expect ( dispatchParams ?. ctx . To ) . not . toBe ( "imessage:+15550000001" ) ;
1780+
1781+ console . log (
1782+ [
1783+ "[L3 proof #104136] scenario: stale local sender repaired to remote peer" ,
1784+ `[L3 proof #104136] anchorless notification sender: ${ anchorlessNotification . sender } ` ,
1785+ `[L3 proof #104136] authoritative history sender: ${ authoritativeHistory . sender } ` ,
1786+ `[L3 proof #104136] monitor dispatch ctx.To: ${ dispatchParams ?. ctx . To } ` ,
1787+ ] . join ( "\n" ) ,
1788+ ) ;
1789+ } ) ;
1790+
1791+ it ( "suppresses anchorless watch payloads when authoritative history is from-me (#104136)" , async ( ) => {
1792+ const issueGuid = "11111111-1111-4111-8111-111111111111" ;
1793+ const runtime = { error : vi . fn ( ) , exit : vi . fn ( ) , log : vi . fn ( ) } ;
1794+ const anchorlessNotification = {
1795+ id : 9501 ,
1796+ guid : issueGuid ,
1797+ chat_id : 0 ,
1798+ chat_guid : "" ,
1799+ chat_identifier : "" ,
1800+ sender : "+15550000001" ,
1801+ destination_caller_id : "+15550000001" ,
1802+ is_from_me : false ,
1803+ is_group : false ,
1804+ service : "iMessage" ,
1805+ text : "outgoing row with broken direction" ,
1806+ created_at : new Date ( ) . toISOString ( ) ,
1807+ } ;
1808+ const authoritativeHistory = {
1809+ id : 9501 ,
1810+ guid : issueGuid ,
1811+ chat_id : 42 ,
1812+ chat_guid : "iMessage;-;+15550000002" ,
1813+ chat_identifier : "+15550000002" ,
1814+ sender : "+15550000002" ,
1815+ destination_caller_id : "+15550000001" ,
1816+ is_from_me : true ,
1817+ is_group : false ,
1818+ service : "iMessage" ,
1819+ } ;
1820+
1821+ let onNotification : ( ( message : { method : string ; params : unknown } ) => void ) | undefined ;
1822+ const client = {
1823+ request : vi . fn ( async ( method : string , params ?: Record < string , unknown > ) => {
1824+ if ( method === "watch.subscribe" ) {
1825+ return { subscription : 1 } ;
1826+ }
1827+ if ( method === "chats.list" ) {
1828+ return { chats : [ { id : 42 } ] } ;
1829+ }
1830+ if ( method === "messages.history" ) {
1831+ expect ( params ?. chat_id ) . toBe ( 42 ) ;
1832+ return { messages : [ authoritativeHistory ] } ;
1833+ }
1834+ throw new Error ( `unexpected imsg method ${ method } ` ) ;
1835+ } ) ,
1836+ waitForClose : vi . fn ( async ( ) => {
1837+ onNotification ?.( {
1838+ method : "message" ,
1839+ params : { message : anchorlessNotification } ,
1840+ } ) ;
1841+ await Promise . resolve ( ) ;
1842+ await Promise . resolve ( ) ;
1843+ } ) ,
1844+ stop : vi . fn ( async ( ) => { } ) ,
1845+ } ;
1846+ createIMessageRpcClientMock . mockImplementation ( async ( params ) => {
1847+ if ( ! params ?. onNotification ) {
1848+ throw new Error ( "expected iMessage notification handler" ) ;
1849+ }
1850+ onNotification = params . onNotification ;
1851+ return client as never ;
1852+ } ) ;
1853+
1854+ await monitorIMessageProvider ( {
1855+ config : {
1856+ channels : {
1857+ imessage : {
1858+ dmPolicy : "allowlist" ,
1859+ allowFrom : [ "+15550000002" ] ,
1860+ } ,
1861+ } ,
1862+ messages : { inbound : { debounceMs : 0 } } ,
1863+ session : { mainKey : "main" } ,
1864+ } as never ,
1865+ runtime,
1866+ } ) ;
1867+
1868+ await vi . waitFor ( ( ) => {
1869+ expect ( runtime . error ) . toHaveBeenCalled ( ) ;
1870+ } ) ;
1871+ expect ( dispatchInboundMessageMock ) . not . toHaveBeenCalled ( ) ;
1872+ expect ( runtime . error . mock . calls . at ( - 1 ) ?. [ 0 ] ) . toContain (
1873+ "recovered authoritative row is from-me" ,
1874+ ) ;
1875+
1876+ console . log (
1877+ [
1878+ "[L3 proof #104136] scenario: authoritative from-me row suppressed before dispatch" ,
1879+ `[L3 proof #104136] notification is_from_me: ${ anchorlessNotification . is_from_me } ` ,
1880+ `[L3 proof #104136] history is_from_me: ${ authoritativeHistory . is_from_me } ` ,
1881+ `[L3 proof #104136] monitor dispatch count: ${ dispatchInboundMessageMock . mock . calls . length } ` ,
1882+ `[L3 proof #104136] runtime error: ${ runtime . error . mock . calls . at ( - 1 ) ?. [ 0 ] } ` ,
1883+ ] . join ( "\n" ) ,
1884+ ) ;
1885+ } ) ;
1886+
16951887 it ( "merges a command row with the following URL balloon row" , async ( ) => {
16961888 // Apple's command+URL composition can arrive as a command row followed by a
16971889 // URL-preview balloon row. The opt-in coalescer keeps the pair as one agent
0 commit comments