fix(routing): unify session delivery invariants for duplicate suppression (#33786)

* Routing: unify session delivery invariants

* Routing: address PR review feedback

* Routing: tighten topic and session-scope suppression

* fix(chat): inherit routes for per-account channel-peer sessions
This commit is contained in:
Tak Hoffman
2026-03-03 21:40:38 -06:00
committed by GitHub
parent 1be39d4250
commit 7f2708a8c3
12 changed files with 436 additions and 28 deletions

View File

@@ -24,16 +24,45 @@ describe("delivery context helpers", () => {
expect(normalizeDeliveryContext({ channel: " " })).toBeUndefined();
});
it("merges primary values over fallback", () => {
it("does not inherit route fields from fallback when channels conflict", () => {
const merged = mergeDeliveryContext(
{ channel: "whatsapp", to: "channel:abc" },
{ channel: "slack", to: "channel:def", accountId: "acct" },
{ channel: "telegram" },
{ channel: "discord", to: "channel:def", accountId: "acct", threadId: "99" },
);
expect(merged).toEqual({
channel: "whatsapp",
to: "channel:abc",
channel: "telegram",
to: undefined,
accountId: undefined,
});
expect(merged?.threadId).toBeUndefined();
});
it("inherits missing route fields when channels match", () => {
const merged = mergeDeliveryContext(
{ channel: "telegram" },
{ channel: "telegram", to: "123", accountId: "acct", threadId: "99" },
);
expect(merged).toEqual({
channel: "telegram",
to: "123",
accountId: "acct",
threadId: "99",
});
});
it("uses fallback route fields when fallback has no channel", () => {
const merged = mergeDeliveryContext(
{ channel: "telegram" },
{ to: "123", accountId: "acct", threadId: "99" },
);
expect(merged).toEqual({
channel: "telegram",
to: "123",
accountId: "acct",
threadId: "99",
});
});
@@ -103,7 +132,7 @@ describe("delivery context helpers", () => {
});
});
it("normalizes delivery fields and mirrors them on session entries", () => {
it("normalizes delivery fields, mirrors session fields, and avoids cross-channel carryover", () => {
const normalized = normalizeSessionDeliveryFields({
deliveryContext: {
channel: " Slack ",
@@ -118,12 +147,11 @@ describe("delivery context helpers", () => {
expect(normalized.deliveryContext).toEqual({
channel: "whatsapp",
to: "+1555",
accountId: "acct-2",
threadId: "444",
accountId: undefined,
});
expect(normalized.lastChannel).toBe("whatsapp");
expect(normalized.lastTo).toBe("+1555");
expect(normalized.lastAccountId).toBe("acct-2");
expect(normalized.lastThreadId).toBe("444");
expect(normalized.lastAccountId).toBeUndefined();
expect(normalized.lastThreadId).toBeUndefined();
});
});

View File

@@ -121,11 +121,23 @@ export function mergeDeliveryContext(
if (!normalizedPrimary && !normalizedFallback) {
return undefined;
}
const channelsConflict =
normalizedPrimary?.channel &&
normalizedFallback?.channel &&
normalizedPrimary.channel !== normalizedFallback.channel;
return normalizeDeliveryContext({
channel: normalizedPrimary?.channel ?? normalizedFallback?.channel,
to: normalizedPrimary?.to ?? normalizedFallback?.to,
accountId: normalizedPrimary?.accountId ?? normalizedFallback?.accountId,
threadId: normalizedPrimary?.threadId ?? normalizedFallback?.threadId,
// Keep route fields paired to their channel; avoid crossing fields between
// unrelated channels during session context merges.
to: channelsConflict
? normalizedPrimary?.to
: (normalizedPrimary?.to ?? normalizedFallback?.to),
accountId: channelsConflict
? normalizedPrimary?.accountId
: (normalizedPrimary?.accountId ?? normalizedFallback?.accountId),
threadId: channelsConflict
? normalizedPrimary?.threadId
: (normalizedPrimary?.threadId ?? normalizedFallback?.threadId),
});
}