Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(crm): resolve internal domains from the org, not just the seat's mailbox

merged#2607CedarCopilot

CedarCopilot wants to merge 6 commits into staging from fix/org-internal-domains

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 27, 2026, 12:39 PM
  2. Aug 27, 2026, 2:18 PM
  3. Merged
    Aug 27, 2026, 3:27 PM
  4. Live on prod
    Aug 27, 2026, 3:27 PM
  5. Observed 3 days
    Aug 27, 2026, 3:27 PM
  6. Pipelines steady after this deploy
    Aug 27, 2026, 3:27 PM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 30, 2026, 3:02 PM

Behaviors Libra is checking

Calendar sync behavior from cron/roll-forward-stale-calendar-events.ts, crm/calendar-events.ts, workflows/calendar-sync.ts keeps working in prod.Degradedlow confidence

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.

prod, checked Aug 30, 2026, 3:02 PM
CRM integration behavior from crm/internal-domains.ts, crm/utils.ts keeps working in prod.Inconclusivelow confidence

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.

prod, checked Aug 30, 2026, 2:01 PM

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.

The bug

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):

  • Liam (<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.
  • Liam's calendar event linked there. The meeting recording then adopted that link through the meeting-calendar-backstop, overriding participant routing that had twice resolved getscalable.ai correctly.
  • Liam's post-meeting run no-op'd ("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.

The fix

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:

filesite
services/crm/calendar-events.tsdetermineSignificance
services/crm/calendar-events.tstrulyExternalAttendees (sync)
services/crm/calendar-events.tstrulyExternalAttendees (enrich)
services/crm/calendar-events.tsfindConversationFromMeetingCalendarLink internal-attendee test
mastra/utils/company-domain-assignment.tsdomain-identification prompt's internal-domain note
mastra/utils/company-domain-assignment.tsreCorrectDomainIfNeeded, which re-runs that identification
mastra/tools/event-execution/assignEventToConversationTool.tsresolveConversationFromSelection 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

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/src/cron/roll-forward-stale-calendar-events.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/__tests__/unattributed-event.test.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/assignEventToConversationTool.tsno production surface mapped
  • apps/server/src/mastra/utils/company-domain-assignment.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/calendar-events-sync.test.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/internal-domains.test.tsno production surface mapped
  • apps/server/src/services/crm/calendar-events.tsno production surface mapped
  • apps/server/src/services/crm/internal-domains.tsno production surface mapped
  • apps/server/src/services/crm/utils.tsno production surface mapped
  • apps/server/src/workflows/__tests__/calendar-sync.test.tsno production surface mapped
  • apps/server/src/workflows/calendar-sync.tsno production surface mapped