CedarCopilot wants to merge 1 commit into staging from fix/staging-main-greptile-findings
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.
Fixes the 5 Greptile findings surfaced on #2838 (staging→main), per Isabelle's request to fix them in their own PR while #2838 itself merges.
CrmAgentBadge.tsx/crm.ts: unlinking the account on a conversation that also had a real recordId link (a Lead/Partnership, which , like an account-only placeholder , has no dealId) silently deleted that record link too. Fixed on both sides:
hasRecord is true, mirroring the existing hasDeal rule.filterIntegrationMetadataForUnlink, now directly unit-tested) requires neither dealId nor recordId before removing an entry as a "placeholder."backfill-crm-object-records.ts: the repeat-until-exhausted loop's fetch is a fixed-size, sorted-newest-first, since-bounded query with no upper-bound cursor. Once the true total exceeds one fetch's cap, older records are permanently unreachable , every repeated pass returns the identical top-N and the script declared success.
possiblyCapped to DiscoverNewDealsResult: true when records were found but all were already linked (not proof of exhaustion).maxPages (HubSpot's getAllDeals already supports this , just never threaded through) and unclampedMaxDeals (the 500-per-pass ceiling exists to protect the on-demand chat tool from an unbounded burst, not a deliberate backfill run).--fetch-max-pages for HubSpot, or re-run with an earlier --since for Salesforce/Attio, which have no page-count lever) instead of silently claiming victory.deal-metrics.ts's aop_conversion classifier collapsed "AOP resolved with no object binding" (Spam, Vendor, any unbound AOP) and "deleted/unresolvable AOP id" into the same value as the true Deals AOP , so a Lead-bound conversation moved to Spam counted as a 100% conversion. classifyAopForConversion now only returns 'deal' for an explicit Opportunity binding or the implicit default (no binding, named literally "Deals"); everything else is 'unknown', never a conversion target.
process-external-crm-sync.ts cast connectionMetadata: unknown directly. Added a real isRecord type guard.
crm-integration-card.tsx's new AOP picker manually composed Label + Select instead of the shared form-kit. Rebuilt with SelectField/FieldRows.
pnpm --filter @zero/server run types cleanpnpm --filter @zero/mail run types clean (touched files)pnpm dlx oxlint --deny-warnings cleanpnpm deps:check clean🤖 Generated with Claude Code
The PR is not safe to merge until the backfill can distinguish successful exhaustion from a capped response and provides recovery controls that actually advance every supported provider path; the explicit fixture-typing rule must also be satisfied.
Libra has not measured any production surfaces for this change yet.