Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(f8): step 4 repair script for vendor buckets recorded as deals (NOT RUN)

draft#2698CedarCopilot

CedarCopilot wants to merge 1 commit into staging from chore/f8-step4-vendor-bucket-repair

Not deployedTimeline and evidence
  1. Opened
    Sep 6, 2026, 2:37 PM
  2. Sep 6, 2026, 2:41 PM
  3. Merged, live on staging, live on prod, observed
    Pending
  4. Not deployed

    Not deployed

    Pending

Libra has no production signal for this change yet because it has not deployed. Libra checks hourly for 3 days after each deploy.

Not executed. Dry-run reviewed and attached below. This renames live conversations, so it needs a human go/no-go before APPLY=1.

The incoherent record

A conversation's primary company domain is the vendor's (fathom.video) while its NAME is a customer's ("Broadband Hospitality") and it carries a deal stage. Both cannot be true. The domain is the honest one: these conversations contain zero email from the company they are named after.

That incoherence is precisely what stops findOrCreateVendorConversation distinguishing a bucket from a deal when both carry the vendor's domain, so repairing it is what lets Step 3 use a plain domain lookup. See docs/design/f8-vendor-conversation-routing.md.

Per conversation: name → the vendor, status → NULL, aopId → the user's no-op Vendor notification AOP.

Two blind spots found by measurement before this ever ran

Both are now predicates with tests, because I got the selection wrong twice.

1. Email-only evidence is not sufficient. A genuine deal can have every EMAIL be a Circleback or Fathom recap while the real relationship lives in meetings. Of 98 email-only matches:

count
had meetings15
had outbound18
were closed_won18

Requiring the conversation to be inert (no meeting, no outbound event) drops those.

2. A booked outcome is never repaired. 23 inert matches carried won/lost/closed, including a closed_won whose only trace is 90 DocuSign notices. That is the shape of a deal worked off-channel where only the signature flow reached email, and nulling its stage would remove a real win from the pipeline.

98 → 69 → 46 conversations across 16 users. Also asserts no linked external CRM deal.

Reversibility

The rename discards the old conversation name. Every old → new pair prints before any write, and the dry run is the record. Events, tasks and history are untouched.

What this does NOT resolve

The 23 booked-outcome conversations stay incoherent by design. So a domain-only lookup in Step 3 would still adopt them, which means Step 3 needs a residual discriminator regardless of this repair. Worth deciding before Step 3 is built.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NVHnt2pEEjhVwSyhkcaxoC

Greptile Summary

This PR adds dry-run-by-default maintenance tooling to identify vendor-dominated conversations, rename eligible records as vendor buckets, clear their deal status, and optionally assign a no-op AOP. It also adds selection-unit tests, executable bundling wrappers, and extensive design/incident documentation.

  • Selects inert conversations using inbound sender composition, event activity, deal linkage, and status predicates.
  • Repairs eligible conversations and includes a companion task-cancellation script.
  • Documents the broader F8 routing workstream and several task/save incident investigations.
  • Requires corrections before execution because the repair currently triggers the imported cleanup script and contains destructive-selection blind spots.

Confidence Score: 0/5

This PR is not safe to merge or execute until the unintended cleanup invocation, destructive selection errors, race protection, and production-data exposure are resolved.

Running the repair currently executes both maintenance scripts, while raw-status outcome detection and unanchored sender matching can erase legitimate deal state or tasks; the ID-only writes can also act on conversations that became protected after selection, and the added public documentation exposes identifiable production records.

Files Needing Attention: apps/server/scripts/repair-vendor-bucket-conversations.ts, apps/server/scripts/cancel-vendor-bucket-tasks.ts, docs/design/f8-vendor-conversation-routing.md, docs/design/stale-task-failure-modes.md, docs/design/refused-save-client-state.md

Security Review

The ad

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/scripts/cancel-vendor-bucket-tasks.tsno production surface mapped
  • apps/server/scripts/repair-vendor-bucket-conversations.tsno production surface mapped
  • apps/server/scripts/run-cancel-vendor-bucket-tasks.mjsno production surface mapped
  • apps/server/scripts/run-repair-vendor-bucket-conversations.mjsno production surface mapped
  • apps/server/tests/scripts/cancel-vendor-bucket-tasks-selection.test.tsno production surface mapped
  • apps/server/tests/scripts/repair-vendor-bucket-conversations-selection.test.tsno production surface mapped
  • docs/design/f8-agent-log.mdno production surface mapped
  • docs/design/f8-vendor-conversation-routing.mdno production surface mapped
  • docs/design/refused-save-client-state.mdno production surface mapped
  • docs/design/stale-task-failure-modes.mdno production surface mapped
  • docs/design/task-completion-write-path.mdno production surface mapped