CedarCopilot wants to merge 3 commits into staging from fix/vendor-bucket-lookup-skips-deals
Live on prod, no production signal yet
Unable to complete verification because telemetry returned no matching OTEL spans and the source read needed to map the operation to concrete production signals was blocked by the source character budget.
Unable to complete verification: telemetry shows 26 concrete post-deploy executions of findOrCreateVendorConversation, including repeated vendor-conversation creation logs, but no confirmed reuse/returned-row success signal or qualifying-row evidence. Source reads were.
Since deployment, CloudWatch shows 26 successful [findOrCreateVendorConversation] vendor-conversation creations across multiple vendors and 3 SQL-parameter log lines containing conversationScope='__vendor_bucket__' for a Luma insert. No exact 'Failed to create or find vendor.
Libra has verdicts on 0 of 5 tracked behaviors on prod; 5 are still being checked. Libra checks hourly for 3 days after each deploy.
F8, Step 3 , the last code change in the vendor-routing workstream.
services/crm/conversations.ts:1837-1857 (findOrCreateVendorConversation) looked the bucket up by primaryCompanyId and took the oldest match:
const companyRelation = await findOrCreateCompanyRelationship(db, userId, vendorDomain);
const lookup = () => db.select({ id: crmConversations.id }).from(crmConversations)
.where(and(eq(crmConversations.userId, userId),
eq(crmConversations.primaryCompanyId, companyRelation.id)))
.orderBy(asc(crmConversations.createdAt)).limit(1);
const existing = await lookup();
if (existing[0]) return existing[0].id; // returns early
// ... create a real bucket <- unreachable once anything is bound
It returns the oldest conversation bound to the vendor's company, whatever that conversation actually is. When a deal conversation was contaminated before the domain was ever added to VENDOR_DOMAINS, that contaminated conversation is by construction the older one, so the vendor route resolves straight back into it and the create branch never runs.
The comment above the lookup states the intent , it guards against a deal the user named "Zoom". It does not guard against a binding that is already wrong, which is the state every pre-contaminated account is in.
fathom.video was added to VENDOR_DOMAINS on 2026-08-20 (6564c3d76). After that date 33 more Fathom emails still landed on <email>'s "Broadband Hospitality" deal , every one of them the first message of a new thread, so the thread short-circuit in assignEventToConversationTool does not explain it , while her correctly named "fathom" conversation received none. Adding a domain to the list after contamination is a no-op by construction.
The plan originally aimed to enforce the invariant "a conversation's primary domain is its counterparty's domain" and then let the lookup key on domain alone. Three measurements say that is not reachable, so the lookup needs a deterministic guard regardless of how good the naming rule gets. This reverses an earlier design call, deliberately:
closed_won whose only trace is 90 DocuSign notices is plausibly a real deal worked off-channel.updateConversationFieldsTool.ts:139-152, which forbids writing name when the primary domain is in VENDOR_DOMAINS , cementing a wrong name as firmly as a right one.The one-predicate fix from the design doc (isNull(status) on the lookup) is necessary but not sufficient, and the test caught it before it shipped.
idx_crm_conversations_unique_user_company_scope (db/crm-schema.ts:568-570) permits exactly one conversation per (user_id, primary_company_id, COALESCE(conversation_scope, '')) where the company is set. On a contaminated account the deal already holds that unscoped slot on the vendor's company row, so the newly reachable create raises 23505. The old catch then re-ran the same status-filtered lookup, found nothing, and threw:
Error: [findOrCreateVendorConversation] Failed to create or find vendor conversation for fathom.video
assignEventToConversationTool.ts:589 does not catch that, so the guard alone would have converted "one email lands in the wrong place" into "the whole event assignment fails" , on exactly the population the fix exists for. findOrCreateInternalConversation two functions down already documents the constraint the vendor function forgot ("The unique index on (user_id, primary_company_id) means only one conversation can exist per company relation").
So
Libra has not measured any production surfaces for this change yet.