Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(sync): persist AOP at selection + self-heal audit→setup handoff

merged#2512CedarCopilot

CedarCopilot wants to merge 0 commits into staging from fix/audit-to-setup-handoff

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 16, 2026, 4:36 PM
  2. Merged
    Aug 16, 2026, 6:49 PM
  3. Live on prod
    Aug 16, 2026, 6:49 PM
  4. Observed 2 days
    Aug 16, 2026, 6:49 PM
  5. Pipelines steady after this deploy
    Aug 16, 2026, 6:49 PM
  6. Unobserved

    Live on prod, no production signal yet

    Aug 19, 2026, 2:49 AM

Behaviors Libra is checking

CRM integration behavior from account-setup/verify-audit-handoff.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 45 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Value \), up from 1 in the comparable baseline, but this domain-wide failure family is not tied to this intent.

prod, checked Aug 19, 2026, 2:49 AM
Email workflow behavior from initial-sync/finalize-steps.ts, initial-sync/handlers.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 3 prod failure log lines for mirror.upsertEmailThread failed (write CONNECT_TIMEOUT aws-1-us-east-2.pooler.supabase.com:6543), but this domain-wide failure family is not tied to this intent.

prod, checked Aug 19, 2026, 2:49 AM

Libra has verdicts on 0 of 2 tracked behaviors on prod; 2 are still being checked. Libra checks hourly for 3 days after each deploy.

Problem

Onboarding a customer data-audit-first left conversations in a state the normal sync could not repair. On Vooma this hid 267 conversations (real deals + prospects) from every board, and recovering it took hours of manual DB surgery.

Root cause (two coupled issues):

  1. AOP selection was decoupled from persistence. resolveAopStep selects the AOP but writes crm_conversations.aop_id only via the optional setupAop step (setupConversationForAop). The extract-org-fields / data-audit preset runs setupAop=false, so the AOP was chosen , and later recorded in agent_executions , but never written to the conversation.
  2. The idempotency skip is unconditional. handlers.ts skips any conversation whose events all have execution records before AOP selection, and --overwrite doesn't bypass it. So the half-finalized state was permanent , no re-run (fresh trigger-sync, extract-org-fields --overwrite) could route it; they no-op'd in ~2 min.

Fix

  • finalize-steps.ts , persist the AOP at selection (root fix): a lightweight aop_id column write the moment an AOP is selected, independent of setupAop. Org-scope merge/dedup is unchanged (still done by setupConversationForAop when setupAop runs), so no extra dedup pass on the normal path. Prevents the bug for all future onboardings.
  • handlers.ts , self-heal on the skip: when a conversation is skipped as already_finalized but its aop_id is NULL while an execution recorded one, backfill it. Repairs existing broken accounts on their next sync, cheaply (reuses the recorded classification , no LLM re-selection). Emits initial_sync_phase2_backfill_aop.
  • verify-audit-handoff.ts , customer-agnostic readiness check + REPAIR mode (backfills the aop_id gap, optional task trim). Turns the manual debugging into a green/red readout.
  • docs/design/audit-to-setup-handoff.md , full root-cause writeup + the remaining follow-ups.
  • SKILL.md , field-tested audit→setup lessons.

Deliberately not changed: the idempotency skip condition. With persist-at-selection + self-heal the problem is solved; asserting "real completion" at the skip would force an expensive selectAop LLM call per broken conversation (cost-regression risk). Left as a reviewed follow-up.

Verification

  • pnpm --filter @zero/server run types , clean on changed files.
  • pnpm deps:check , no dependency violations (1427 modules).
  • Validated the repair tool + backfill against Vooma live (267 conversations routed; 0 duplicates; HubSpot 1:1 intact).
  • Ran thermo-review, which caught (and I fixed pre-push) a redundant double setConversationAop/dedup pass , the persist is now a lightweight column write.

⚠️ Follow-up required before/at merge

The Phase-2 finalize handler + resolveAopStep have no integration tests today (only the step-flag helpers are unit-tested). This PR's changes are on the Gmail-sync path , handler integration tests covering the skip / self-heal / persist paths should land with or right after this. Tracked in the design doc.

🤖 Generated with Claude Code

Greptile Summary

This PR persists selected AOPs independently of optional setup, self-heals previously finalized conversations whose AOP was recorded only in executions, and adds audit-handoff verification and operational guidance.

  • Adds lightweight AOP persistence during selection and NULL-only backfill during idempotency skips.
  • Adds a read-only-by-default verification/repair script with optional task trimming.
  • Adds focused persistence tests and documents the audit-to-setup recovery process.

Confidence Score: 4/5

The AOP persistence changes appear sound, but the repair script should validate every supplied user against ORG_ID before it can safely mutate conversations or trim tasks.

Explicit USER_IDS bypass organization membership filtering, after which repair and tas

Show production surfaces and changed-file mapping

Production surfaces

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

Changed files → surfaces

  • .claude/skills/account-setup-from-transcript/SKILL.mdno production surface mapped
  • apps/server/src/db/migrations/scripts/account-setup/verify-audit-handoff.tsno production surface mapped
  • apps/server/src/services/mail/initial-sync/__tests__/resolve-aop-step.test.tsno production surface mapped
  • apps/server/src/services/mail/initial-sync/finalize-steps.tsno production surface mapped
  • apps/server/src/services/mail/initial-sync/handlers.tsno production surface mapped
  • docs/design/audit-to-setup-handoff.mdno production surface mapped