CedarCopilot wants to merge 4 commits into staging from worktree-agent-aad1abeaa9c583f99
Live on prod, 0 of 14 surfaces working, 2 days left
Libra found 14 production surfaces on prod but could not judge any of them yet. 6 surfaces had under 20 requests, so Libra has not judged them; 4 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.
Third instance of the silent-no-op bug family fixed by #2882 (filters array) and #2886 (eventFilter), both in conversation-lookup.ts. This one is in a different file/call path: buildConversationFilterConditions (apps/server/src/services/crm/conversations.ts) , the column_filters mode / frontend CRM view + inbox-rule call path, not flexible_query.
Symptom, same shape as the other two: eventFilter.types.include/.exclude, latestEventFilter.types, and eventFilter.count.eventType all silently dropped an explicit-but-invalid event type instead of throwing , .filter((c): c is SQL => c !== null) quietly excluded a bad entry from an array while valid siblings still applied, and if (typeCondition) { ...push... } silently dropped the whole count filter. The tool/query still reported success against an unfiltered-or-partially-filtered result.
The fix: every one of those four sites now throws for an explicit-but-invalid value (never silently drops), while the genuinely-empty-criteria case (no types at all, no date) remains a legitimate no-op , verified by dedicated tests for both.
A second, bigger finding surfaced during review, and fixed here too (not deferred): the two blocks each declared their own byte-for-byte-identical local VALID_EVENT_TYPES Set. More importantly, tracing every caller of buildConversationFilterConditions found one with a materially worse blast radius than a one-off agent query failing , a saved inbox CRM rule (InboxConfig.conversationFilter) flows through this same builder on every inbox load (list-threads-from-db.ts, count-threads-from-db.ts), with no try/catch anywhere in between. Before this diff, a bad saved rule silently mis-filtered; after a naive version of this fix, it would throw on every subsequent load of that inbox , a materially worse outcome than a chat-agent tool call getting a clear retry-able error.
Live-checked production (user_settings.settings ILIKE '%eventFilter%'/%latestEventFilter%') , zero rows currently use either field, so this isn't an active incident. But nothing in the schema prevented one from being saved. Fixed at the write boundary instead of leaving it as a read-time landmine:
apps/server/src/services/crm/event-types.ts , the canonical VALID_EVENT_TYPES + isValidEventType/assertValidEventType. Zero imports, so importing it doesn't reintroduce the heavy conversations.ts import-graph cost conversation-filter-subquery.ts's lazy import() exists specifically to avoid (verified: conversation-filter-schema.ts's existing conversations.ts import stays import type-only, unchanged).conversations.ts imports it for the SQL condition builders (throws , defense in depth at read time).conversation-filter-schema.ts imports it for a zod .refine() on eventFilter.types.include/.exclude/latestEventFilter.types, so a bad value is now rejected with a clean validation error at save time, before it can ever reach the read-time throw.Deliberately NOT reconciled: conversation-lookup.ts's own separate VALID_EVENT_TYPES (task/linkedin there vs. slack/custom here) , different call paths, independently-evolved lists. Figuring out which is actually correct requires checking real crm_events.event_type values in production, a data question not a code fix , out of scope here, same as it was for #2886.
pnpm --filter @zero/server run types , cleanpnpm deps:check , 0 violations (2010 modules)conversations.event-filter-conditions.test.ts (15 tests): invalid type in types.include/.exclude/count/latestEventFilter.types all throw; valid values across the real VALID_EVENT_TYPES set (iterated, not hardcoded) all succeed; the genuinely-empty-criteria case stays a no-op; unparseable dates already threw pre-existing (Date#toISOString on an Invalid Date) , verified,| Surface | Requests | Errors | p95 | Users | Verdict |
|---|---|---|---|---|---|
| /api/trpc/crm.searchConversationsMinimal | 0 → 3 | 0 → 0 (0%) | not measured → 135 ms | 0 | Insufficient traffic 3 requests, under the 20 Libra needs |
| /api/trpc/crm.listConversations | 0 → 3 | 0 → 0 (0%) | not measured → 1016 ms | 0 | Insufficient traffic 3 requests, under the 20 Libra needs |
| POST /api/trpc/crm.searchConversationsMinimal | 0 → 2 | 0 → 0 (0%) | not measured → 135 ms | 0 | Insufficient traffic 2 requests, under the 20 Libra needs |
| POST /api/trpc/crm.listConversations | 0 → 2 | 0 → 0 (0%) | not measured → 1016 ms | 0 | Insufficient traffic 2 requests, under the 20 Libra needs |
| trpc.crm.searchConversationsMinimal | 0 → 1 | 0 → 0 (0%) | not measured → 123 ms | 1 | Insufficient traffic 1 request, under the 20 Libra needs |
| crm.searchConversationsMinimal | 0 → 1 | 0 → 0 (0%) | not measured → 122 ms | 1 | Insufficient traffic 1 request, under the 20 Libra needs |
| trpc.crm.loadCalendarConversationsBatch | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/crm.loadCalendarConversationsBatch | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| POST /api/trpc/crm.loadCalendarConversationsBatch | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/crm.getConversations | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| buildConversationFilterConditions.eventFilter.count | no baseline → 0 | no baseline → 0 (0%) | not measured | not counted | Unavailable ClickHouse reads are unavailable, so Libra could not measure this surface. |
| buildConversationFilterConditions.eventFilter.types.exclude | no baseline → 0 | no baseline → 0 (0%) | not measured | not counted | Unavailable ClickHouse reads are unavailable, so Libra could not measure this surface. |
| buildConversationFilterConditions.eventFilter.types.include | no baseline → 0 | no baseline → 0 (0%) | not measured | not counted | Unavailable ClickHouse reads are unavailable, so Libra could not measure this surface. |
| buildConversationFilterConditions.latestEventFilter.types | no baseline → 0 | no baseline → 0 (0%) | not measured | not counted | Unavailable ClickHouse reads are unavailable, so Libra could not measure this surface. |