CedarCopilot wants to merge 4 commits into staging from fix/eventfilter-silent-no-op-and-range-cast
Live on prod, watching, 2 days left
Libra has verdicts on 0 of 7 tracked behaviors on prod; 7 are still being checked. Libra checks hourly for 3 days after each deploy.
Sibling fix to #2882, which fixed conversation-lookup.ts's filters array to throw instead of silently returning null for an unsupported field/operator combination , a bug traced from a real production incident where a chat query ran unfiltered and reported success: true. That PR deliberately scoped out the eventFilter path, which had the identical bug. This PR closes it.
eventFilter fix , buildEventTypeCondition, buildEventTypeDateCondition, buildEventCountCondition in conversation-lookup.ts now throw for anything they were given but couldn't use (an unrecognized event type/direction, an invalid types.include/exclude entry, an unparseable date, a bad count operator), instead of silently dropping it from the WHERE clause. buildEventTypeDateCondition still correctly returns null for the one genuinely empty case , no types, no date, i.e. no real constraint given , so "asked for nothing" and "asked for something invalid" stay distinguishable.
Regression-proofing (the actual ask behind this PR): added const _exhaustive: never = ... compile-time exhaustiveness guards on every switch over a closed union in this file (FilterOperator ×2, eventFilter.dateOperator, eventFilter.count.operator) , verified live, not just written and assumed to work: temporarily added a 13th fake FilterOperator, confirmed tsc failed the build at both call sites with Type '"fakeTestOperator"' is not assignable to type 'never', then reverted. The new test file iterates the real exported VALID_EVENT_TYPES Set rather than a hardcoded copy, so a type added there later is automatically covered without touching the test.
Second bug, found while fixing the first , EventFilterSchema.dateOperator (findCRMConversationsTool.ts) includes 'range' because column_filters mode genuinely implements it (via dateTo, in conversations.ts's own filter builder , untouched by this PR). flexible_query mode never implemented range support at all , LookupEventFilter.dateOperator only ever declared before/after/on , so a 'range' value used to fall through an unhandled switch case silently (zero clauses, false success). The exhaustiveness fix above would have turned that into an uncaught-looking throw from deep inside conversation-lookup.ts instead of a clean tool error. Fixed by rejecting 'range' explicitly in flexible_query mode with a real, actionable error message, before any DB work , narrowed via a local const, no cast, replacing the as 'before' | 'after' | 'on' | undefined cast that previously papered over the mismatch (this repo's NEVER-cast rule).
Known, unfixed, out-of-scope follow-up , conversations.ts (column_filters mode's own filter builder) has three separate hand-copied implementations of this same event-type validation pattern (buildEventTypeCondition for the main event filter, a second copy for latestEventFilter, plus the .filter((c): c is SQL => c !== null) silent-drop this whole PR series exists to eliminate), each with its own VALID_EVENT_TYPES Set , and those Sets have already drifted: conversations.ts has task/linkedin where this file has slack/custom. Same silent-no-op bug class, unfixed, in a different file/call path. Not fixed here , same scoping reasoning #2882 used originally. Isabelle is deciding whether to file a follow-up ticket for it.
pnpm --filter @zero/server run types , clean, including after the deliberate exhaustiveness-break experiment was revertedpnpm deps:check , 0 violationsconversation-lookup.event-filter-conditions.test.ts (20 tests): every throw path asserted by message, the one legitimate null case proven not over-fixed, and a lookupConversations-level integration test proving a bad eventFilter never reaches the database (canary-mocked db.select) and surfaces the real error, not a stand-infindCRMConversationsTool.test.ts foLibra has not measured any production surfaces for this change yet.