Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(crm): cross-check deal contact domains before minting a new conversation

merged#2777CedarCopilot

CedarCopilot wants to merge 4 commits into staging from fix/crm-duplicate-conversation-contact-crosscheck

Live on prod, 0 of 3 surfaces degradedTimeline and evidence
  1. Opened
    Sep 13, 2026, 7:44 PM
  2. Sep 14, 2026, 6:15 AM
  3. Live on staging
    Sep 14, 2026, 6:21 AM
  4. Pipelines steady after this deploy
    Sep 14, 2026, 6:21 AM
  5. Merged
    Sep 14, 2026, 6:21 AM
  6. Live on prod
    Sep 14, 2026, 6:21 AM
  7. Observed 2 days, 3 surfaces, 3,609 requests
    Sep 14, 2026, 6:21 AM
  8. Pipelines steady after this deploy
    Sep 14, 2026, 6:21 AM
  9. Mixed signals

    Live on prod, 0 of 3 surfaces degraded

    Sep 17, 2026, 5:28 AM

Behaviors Libra is checking

When multiple different contact domains each have corroborating contacts, the system refuses to select a company based on database lookup order and falls through to agent-based resolution.Brokenlow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

staging, checked Sep 17, 2026, 5:30 AM
Contact domains are normalized and counted across the first five contacts, and a domain qualifies for automatic matching only when at least two contacts share it, preventing a lone partner or consultant from determining routing.Degradedmedium confidence

Strict CloudWatch fallback found 30 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \) in the last 24h while checking extractcorroboratedcontactdomains, contactcount, corroborateddomaincount, assigneventtoconversationtool.

prod, checked Sep 17, 2026, 5:28 AM
Deals with multiple corroborated domains, no repeated domain, or no known-company match do not let database lookup order select a company and instead continue through the agent-based assignment path.Degradedmedium confidence

Strict CloudWatch fallback found 31 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \) in the last 24h while checking assigneventtoconversation, assigneventtoconversationtool, outcome":"ambiguous_multi_domain", outcome":"no_corroboration".

prod, checked Sep 17, 2026, 3:43 AM
External CRM deal assignment fetches up to five associated contacts and uses their email domains as additional identity evidence before creating or routing the conversation.Degradedmedium confidence

Strict CloudWatch fallback found 31 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \) in the last 24h while checking assigneventtoconversation, contactcount, assigneventtoconversationtool, external_crm_contact_crosscheck.

prod, checked Sep 17, 2026, 3:43 AM
A CRM contact-fetch failure or a deal with no contacts never blocks conversation assignment and falls back to the prior deal-company/domain-identification behavior without contact evidence.Degradedmedium confidence

Strict CloudWatch fallback found 30 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \) in the last 24h while checking /domain-identification, assigneventtoconversation, assigneventtoconversationtool, record with, ; empty results report, up.

prod, checked Sep 17, 2026, 5:28 AM
For contact-bearing fallback cases, domain identification receives the real CRM contact emails as participants plus the CRM deal company name and domain as an explicit candidate to verify with lookup-company-fuzzy rather than assuming the participant domain is a new company.Degradedmedium confidence

Strict CloudWatch fallback found 31 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \) in the last 24h while checking selectprimarycompany, builddomainidentificationprompt, assigneventtoconversation, assigneventtoconversationtool.

prod, checked Sep 17, 2026, 4:28 AM

Failures attributed to this change

No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 2 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 8:35 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 2 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 8:35 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 1 hit · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 9:21 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 1 hit · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 9:21 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 9:27 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 9:35 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 9:35 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 12 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 14, 2026, 9:35 AM

Libra measured 1 of 3 production surfaces on prod. Libra checks hourly for 3 days after each deploy.

Summary

  • A CRM deal's Company.domain isn't always the domain its real contacts email from , Vooma's Dynamic Logistix deal lists dynamiclogistix.com on the HubSpot Company object, but all 5 of its actual associated contacts are @shipdlx.com. assignEventToConversation's external_crm branch trusted Company.domain alone, so the email/meeting side of an account and the CRM-sync side could mint two separate Cedar conversations for the same real company. Confirmed and merged 9 such split pairs for Vooma this session.
  • On first-ever sighting of a deal (the only point this can originate , routine field syncs go through a programmatic mapping that skips the LLM entirely), the deal's associated contacts are now fetched and cross-checked deterministically (matchGlobalCompanyByAnyDomain , exact domain match only, no fuzzy/LLM judgment) before ever asking the domain-identification agent. On a miss, the real contacts are passed to the agent as participants (previously always []) plus a hint naming the CRM's own company record, so it has a reason to actually verify via lookup-company-fuzzy instead of assuming the participant domain is a brand-new company.
  • Hint wording validated with a scripted eval (crm-known-deal-hint-eval.ts, 10 trials/variant against the real system prompt + model): 2/10 correct without the hint vs. 7/10 with it, tool invoked 10/10 vs. 6/10.
  • Contacts capped at 5, fetch is best-effort (try/catch) , any failure (network, provider outage, an org with no Salesforce Contact Roles configured) degrades to exactly today's behavior.
  • Companion Superglue change (not in this diff , crm-fetch-deals-by-ids is a hosted tool, not a repo file): already built, tested live against real connected accounts on all three providers (HubSpot: the real Vooma deal; Salesforce and Attio: separate live-connected test accounts), and published to production ahead of this PR. It's additive only , a new contacts field nothing reads yet , so merging this PR is what activates the cross-check, not a prerequisite for the tool change being safe.

Test plan

  • pnpm --filter @zero/server run types , clean
  • pnpm deps:check , 0 violations
  • pnpm --filter @zero/server exec vitest run on all 3 touched/new test files , 14/14 passing, re-verified from a clean install in an isolated worktree
  • Live verification of the Superglue tool's contacts output against real connected deals: HubSpot (Vooma, 5/5 correct), Salesforce (correct positive match + correct null-email filtering, confirmed against raw SOQL), Attio (correct match)
  • Thermo-review pass completed; one real gap found and fixed during review (getDealsByIds result now matched by id instead of array position)
  • Post-merge: spot-check a live account with a genuine Company.domain/contact-domain mismatch to confirm no duplicate conversation gets created

🤖 Generated with Claude Code

Greptile Summary

This PR augments first-sighting external CRM deal assignment with associated-contact domain checks and an agent hint intended to avoid duplicate conversations.

  • Fetches up to five deal contacts through the provider’s on-demand deal capability.
  • Routes directly when a contact domain matches an existing global company; otherwise supplies contacts and CRM company context to domain identification.
  • Adds multi-domain lookup support, a normalized contacts field, focused tests, and an offline prompt evaluation harness.
  • The deterministic match currently trusts any associated contact and can consequently link a deal to an unrelated company.

Confidence Score: 4/5

The PR is not safe to merge until deterministic routing distinguishes account contacts from unrelated associated contacts and the explicit no-cast repository requirement is satisfied.

The new first-match shortcut can bypass company identification and permanently link a CRM deal to an unrelated contact’s

Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
db.getConnectionRecord3452 → 30817 → 10 (0.3%)102 ms → 94 ms0Working
3,081 requests since the deploy with 10 errors (7 errors in the 3,452-request baseline before it). p95 94 ms, was 102 ms.
gmail.users.threads.get396 → 50710 → 9 (2%)466 ms → 453 ms95Pre-existing
9 of 507 requests failed (1.8%) since the deploy, matching the 2.5% baseline error rate before it. Not attributable to this change.
aws-primary.syncThread14 → 217 → 10 (48%)804 ms → 550 ms0Pre-existing
10 of 21 requests failed (47.6%) since the deploy, matching the 50% baseline error rate before it. Not attributable to this change.

Changed files → surfaces

  • apps/server/src/mastra/tools/event-execution/assignEventToConversationTool.tsdb.getConnectionRecordgmail.users.threads.getaws-primary.syncThread
  • apps/server/.claude/skills/playbook-instruction-eval/crm-known-deal-hint-eval.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/__tests__/external-crm-contact-crosscheck.test.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/__tests__/external-crm-domain-guards.test.tsno production surface mapped
  • apps/server/src/mastra/utils/company-domain-assignment.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/match-global-company-by-any-domain.test.tsno production surface mapped
  • apps/server/src/services/crm/companies.tsno production surface mapped
  • apps/server/src/services/integrations/crm/driver.tsno production surface mapped