CedarCopilot wants to merge 0 commits into staging from fix/audit-to-setup-handoff
Live on prod, no production signal yet
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.
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.
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.
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):
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.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.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.
pnpm --filter @zero/server run types , clean on changed files.pnpm deps:check , no dependency violations (1427 modules).setConversationAop/dedup pass , the persist is now a lightweight column write.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
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.
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
Libra has not measured any production surfaces for this change yet.