CedarCopilot wants to merge 1 commit into staging from fix/crm-field-sync-multi-deal-guard
Live on prod, no production signal yet
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.
Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.
Strict CloudWatch fallback saw 518,846 success-shaped log lines matching applycrmfieldsync, crm-field-sync-full.test, crm-field-sync, field, sync, but no tied operation was present, so Libra is not calling this working.
Verification reached its budget before a verdict.
Libra has verdicts on 0 of 6 tracked behaviors on prod; 6 are still being checked. Libra checks hourly for 3 days after each deploy.
From Pirros's 2026-09-10 check-in, three bugs were reported: HubSpot closed-won field overwrite, "accept SQL" not filling stage/date, and duplicate email drafts. This PR is the code fix for the second , the other two were config-only and already applied live (no PR needed), included below for full traceability.
Pirros's crm-updater instructions (org template + all 3 reps) explicitly told the agent to populate TCV/ARR/contract dates/etc. on Closed Won , fields Pirros's own separate line-item workflow owns. Also found a second, non-obvious blocker while implementing: those same fields were marked structurally required for the Closed Won stage in stageRequiredFields (HubSpot's raw stage id for Pirros's "Closed Won" label is decoupled from the display label , a common HubSpot clone-artifact gotcha). Removed the instruction block from all 4 docs and trimmed the required-fields list for Closed Won on all 3 Pirros HubSpot connections. Verified via verify-playbook-resolution.ts and verify-document-yjs-sync.ts, all green.
Initial theory (no date-type coercion in hubspot.ts) didn't hold up: another HubSpot-connected customer's date pushes on the identical code path persisted fine. Direct investigation via HubSpot's own property-history API showed both real deals stable since mid-2025 , there is no HubSpot workflow involved at all.
Real root cause: crm-field-sync.ts's active-deal guard (dropStatusWriteFromNonActiveDeal) only protected the status field from being overwritten by a sync event from a linked-but-not-active deal, on the stated theory "only stage is contended this way." That theory was wrong. A Pirros conversation linked to two HubSpot deals , one frozen at sql=false/sql_date=2025-07-02 (human-set 2025-07-02, untouched since), one frozen at sql=true/sql_date=2025-09-25 (human-set 2025-09-26, untouched since) , alternated between those two deals' full value sets on every sync for six weeks, because sql/sql_date/dq_reason had zero protection. The same pattern shows up on a dozen+ other Pirros conversations (216, 195, 194, 184 flips since 2026-08-03).
Fix: generalized the guard (renamed dropNonActiveDealWrites) to drop ALL fields from a non-active-deal sync, not just status, in both call sites (applyCrmFieldSync , the diff-based path, and applyFullProgrammaticCrmMappings , the on-demand full-refresh path). The latter had a second, compounding bug: its integrationMetadata fetch was gated on a built-in CONVERSATION_METADATA_FIELDS column being present in the sync , a sync carrying only custom/working-memory fields (exactly sql/sql_date/dq_reason's shape) never even fetched the metadata needed to run the guard. Now fetched unconditionally whenever there are fields to check.
Keenan and Xander each had two subagents independently allowed to draft a reply to the same inbound email (drafter and inbound-email-notifier, the latter's own instructions saying its job was "two-fold: draft a reply AND send a Slack DM"), racing on every inbound email. This was already diagnosed and fixed once , for Peter only (2026-08-24) , and never applied to Keenan's/Xander's copies, which is why it kept recurring for them specifically. Rewrote both to notify-only, mirroring Peter's fixed copy exactly. Verified via cedar-cli subagent verify and verify-document-yjs-sync.ts, all green.
crm-field-sync.ts is shared code , any customer with multiple deals linked to one conversation is exposed to this, not just Pirros (Pirros's DQ'd-deal + live-re-engagement pattern just makes them hit it often). Bugs 1 and 3 are Pirros-only config.
pnpm --filter @zero/server run types , cleanLibra has not measured any production surfaces for this change yet.