Skip to content

Commit 6ef3e87

Browse files
zw-xysksmcursoragent
authored
fix(imessage): apply authoritative projection in anchorless recovery (#104218)
* fix(imessage): apply authoritative projection in anchorless recovery Rebuild on latest main with imessage-only changes: authoritative history projection (optional destination_caller_id), whole-path inbound proof tests, and no unrelated restart.ts diff. Co-authored-by: Cursor <[email protected]> * fix(imessage): restore recovery cursor API and format tests Co-authored-by: Cursor <[email protected]> * test(imessage): add monitor whole-path L3 proof for #104136 recovery Exercise issue synthetic payloads through monitorIMessageProvider for authoritative remote reply routing and from-me suppression before dispatch. Co-authored-by: Cursor <[email protected]> * fix(imessage): clear stale destination_caller_id when history omits it Exact-GUID history is authoritative for destination_caller_id. When the history row omits that outgoing-only field, clear any stale notification value instead of inheriting it into the recovered inbound projection. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: sm <[email protected]> Co-authored-by: Cursor <[email protected]>
1 parent 4fe5002 commit 6ef3e87

3 files changed

Lines changed: 652 additions & 25 deletions

File tree

extensions/imessage/src/monitor.last-route.test.ts

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)