CedarCopilot wants to merge 6 commits into staging from fix/org-internal-domains
Live on prod, no production signal yet
Strict CloudWatch fallback found 1 prod failure log lines for [CALENDAR_EVENT] Failed to process event (sync-only) (Failed query: insert into \) in the last 24h while checking calendar, meeting, meetings.
Strict CloudWatch fallback found 6 recent prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (fetch failed), but the same failure had 7 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR intent without a post-deploy increase.
Libra has verdicts on 0 of 2 tracked behaviors on prod; 2 are still being checked. Libra has 1 low-confidence degraded verdict it is still confirming. Libra checks hourly for 3 days after each deploy.
Event routing identifies the counterparty by elimination: an attendee whose domain isn't "ours" is a candidate. "Ours" came from the seat's own email address plus its Gmail send-as aliases , nothing else.
Any customer running more than one company domain therefore had teammates read as the counterparty. Those events routed onto whichever deal already carried that domain as a company, and that deal became an attractor that swallowed unrelated meetings.
Concretely, on Corgi (corgi.insure, corgiinsure.com, corgi.com, trycorgi.com, corgicompany.com):
<email>) had a meeting with Scalable AI. Attendees: himself, <email> (the customer), and <email> (a colleague).trycorgi.com was not internal, so it was a routing candidate. lookup-company-fuzzy resolved it to a 232-event catch-all deal owned by a different rep.meeting-calendar-backstop, overriding participant routing that had twice resolved getscalable.ai correctly."No action needed as collaborator , Closed Won stage"). The other rep's run wrote the Scalable recap onto their unrelated deal and overwrote its strategic overview.His deal showed an invite, no meeting, then his own follow-up email. Nothing looked broken from the UI.
organizations.domains already held the right answer and nothing in the CRM routing path read it , it only gated signup auto-join.
New internalDomainsForUser(db, {userId, userEmail, aliasEmails}) returns the seat's own address ∪ its Gmail send-as aliases ∪ the org's configured domains, normalized to eTLD+1, with consumer mail always excluded so a seat on Gmail can't mark every Gmail counterparty a coworker. It never throws: every caller sits on an ingest path where the org lookup is an enrichment, not the point, so a failed read logs and degrades to the address-derived set rather than abandoning the event.
Config is the only source. An earlier revision also inferred domains from the org's seat list; that was removed. Inference is the dangerous direction , a domain wrongly marked internal makes real customer attendees read as coworkers, determineSignificance returns false, and the meeting is dropped with no calendar event and no conversation, silently. Orgs park vendor, consultant and archived seats on live counterparty domains (crustdata.co, fgtmpartners.com, beagleforpm.com all sit on real orgs today), so the inference imported customer domains and needed a status/role heuristic to stay safe. Measured across all 78 orgs it added exactly one domain config didn't already have. An org with an empty column degrades to today's behavior, so a gap is a config task, not a correctness problem.
Seven duplicated derivations now route through it:
| file | site |
|---|---|
services/crm/calendar-events.ts | determineSignificance |
services/crm/calendar-events.ts | trulyExternalAttendees (sync) |
services/crm/calendar-events.ts | trulyExternalAttendees (enrich) |
services/crm/calendar-events.ts | findConversationFromMeetingCalendarLink internal-attendee test |
mastra/utils/company-domain-assignment.ts | domain-identification prompt's internal-domain note |
mastra/utils/company-domain-assignment.ts | reCorrectDomainIfNeeded, which re-runs that identification |
mastra/tools/event-execution/assignEventToConversationTool.ts | resolveConversationFromSelection routing set |
resolveConversationFromSelection unions the org set with the address-derived one rather than replacing it. The org set drops consumer domains by design, but the "agent routed to our own domain → Internal bucket" guard still has to recognise a Gmail seat's own domain, or it would mint a gmail.com deal conversation for them.
The identity handed to selectPrimaryCompany is built once by a single factory. Four hand-written copies is how the email bran
Libra has not measured any production surfaces for this change yet.