Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(crm): guarantee pre-meeting prep for real meetings misclassified as non-deal

merged#2772CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/ambiguous-aop-meeting-prep-floor

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 13, 2026, 12:59 PM
  2. Sep 13, 2026, 1:28 PM
  3. Merged
    Sep 13, 2026, 7:19 PM
  4. Live on prod
    Sep 13, 2026, 7:19 PM
  5. Observed 46 hours, 1 surface, 0 requests
    Sep 13, 2026, 7:19 PM
  6. Pipelines steady after this deploy
    Sep 13, 2026, 7:19 PM
  7. Unobserved

    Live on prod, no production signal yet

    Sep 15, 2026, 5:39 PM

Behaviors Libra is checking

When a meeting is attached to the manifest-less catch-all AOP named "Non-deal professional relationships" or "Professional relationships" and has a non-internal, non-declined external attendee, pre-meeting prep falls back to the user's Deals AOP and is scheduled.Inconclusivelow confidence

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

prod, checked Sep 15, 2026, 4:36 PM
The fallback must not force pre-meeting prep for self-only or internal meetings, meetings whose only external attendee declined, or other correctly classified no-op AOPs such as Spam, Vendor notification, Recruiting, and Inbound cold pitch.Inconclusivelow confidence

Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.

prod, checked Sep 15, 2026, 4:38 PM
Operators receive a structured informational log identifying every ambiguous-AOP meeting that is redirected to Deals prep.Inconclusivelow confidence

Unable to complete the verification because the telemetry loop did not return a final verdict.

prod, checked Sep 15, 2026, 5:37 PM
Meeting-prep status diagnosis follows the same ambiguous-AOP Deals fallback as scheduling so an eligible meeting is not incorrectly reported as lacking a before-meeting configuration.Inconclusivelow confidence

Strict CloudWatch fallback found 177 prod failure log lines for [SCHEDULED] Failed to enrich unenriched calendar event (Failed query: select \), but the failure is not tied to this intent.

prod, checked Sep 15, 2026, 5:39 PM

Libra found 1 production surface on prod but could not judge any of them yet. 1 surface had no requests at all. Libra checks hourly for 3 days after each deploy.

Summary

A conversation can land on Cedar's "Non-deal professional relationships" catch-all AOP from a single thin, early, ambiguous email (e.g. "nice talking, let's catch up next week") and then never get re-classified. That AOP has no playbook manifest by design (it's a no-op bucket), so scheduleBeforeMeetingAutomations silently skips pre-meeting prep for every subsequent meeting on that conversation , including one that's obviously a real external engagement , with no execution/log trace at the skip point itself.

Real case: Vooma / Jason Conlow's "Run Logistics" conversation (rlsqinc.com) scored 0.85 confidence (HIGH band) into this AOP from that one thin email, then got zero pre-meeting prep for a real discovery call 17 days later ("sign you up tomorrow," $50K/year, specific integration requirements). A calendar invite existed days ahead of the call, so this wasn't a missing-signal problem , the invite existed, prep was just never scheduled because of the AOP misclassification.

The fix

When the manifest lookup comes back empty in scheduleBeforeMeetingAutomations, and the resolved AOP is specifically this one catch-all, and the calendar event carries a real (non-internal) external attendee, retry once under the user's own "Deals" AOP so prep still fires. New logic lives in before-meeting-prep-floor.ts, exposing one entry point (resolveAmbiguousAopPrepFallback).

Scoped ONLY to "Non-deal professional relationships." I scoped production data across two real orgs before writing this: no-op-AOP-classified meetings account for 4-19% of a given week's meetings, but the overwhelming majority of those hits (e.g. 4 of 6 at one org) were Vendor notification correctly catching an internal recurring calendar entry (Fathom's own "Start/End of Week" notices) , genuinely correct classification that must NOT get forced prep. Spam, Vendor notification, Recruiting, and Inbound cold pitch are left untouched.

A note on AOP naming

docs/design/ambiguous-aop-classification-escalation.md claims a fleet-wide rename from "Non-deal professional relationships" to "Professional relationships" was already shipped (2026-08-17). Live production data (pulled this week) still shows the old name, and the canonical constant in services/aop/types.ts (AopName.NonDealProfessionalRelationships = 'Non-deal professional relationships') , which is also what the account-setup seeding script still writes , confirms the old name is still the actual ground truth in code today. This PR matches against that stable enum value rather than a raw string, so it's correct regardless of which name is live for a given org, and unaffected if that rename is completed later (the enum's value would need updating too, at which point this code updates for free).

A known minor inefficiency, left as-is

getAop() opens its own DB connection (open + close) rather than accepting the caller's existing connection , pre-existing behavior, already used this way elsewhere in calendar-events.ts for participant AOP remapping. My new floor-guarantee path reuses it the same way. This only fires on the failure/no-manifest path (not the common happy path), so the extra connection cost is low-frequency. Fixing getAop to accept an existing connection would be a separate, broader refactor , out of scope here.

Structural note

calendar-events.ts was already 1,623 lines before this branch (well past the project's 1,000-line guideline). Rather than growing it further, the new floor-guarantee logic (plus the three small helpers it needs) lives in a new before-meeting-prep-floor.ts module. Net effect on calendar-events.ts is +6 lines, not the +83 a naive inline addition would have been.

Test plan

  • New test file ambiguous-aop-meeting-prep-floor.test.ts (4 cases): falls back to Deals prep when the ambiguous AOP has a real external attendee; does NOT force prep when there's no real external attendee; does NOT force prep f
Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
execute_tool list-calendar-events0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.

Changed files → surfaces

  • apps/server/src/services/crm/calendar-events.tsexecute_tool list-calendar-events
  • apps/server/src/services/crm/__tests__/ambiguous-aop-meeting-prep-floor.test.tsno production surface mapped
  • apps/server/src/services/crm/before-meeting-prep-floor.tsno production surface mapped
  • apps/server/src/services/meetings/prep-status.tsno production surface mapped