CedarCopilot wants to merge 2 commits into staging from worktree-send-it-deal-write-guard
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: updateConversationFieldsTool.ts.
Live on prod, 0 of 12 surfaces working, 2 days left
Libra found 12 production surfaces on prod but could not judge any of them yet. 4 surfaces had under 20 requests, so Libra has not judged them; 8 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.
dealWriteGuard (same field -> value shape as dealSyncFilter). When every pair matches the deal's last stored snapshot, the CRM push is skipped before the driver is called. It is enforced at the single updateOpportunityFields call site in update-external-crm-workflow.ts, so agents, chat, approvals and UI edits all pass through it.{ "hs_is_closed": "true" } stops writes to closed deals.{ success: false, skipped: true, skipReason: 'deal_write_guard' }, so callers record skipped, not pushed. An absent guard key or a missing snapshot lets the write through (logged).updateConversation / upsertWorkingMemory treat only the guard skip as a non-error (all other skips still throw as before); updateExternalCrm reports the real workflow result; update-conversation-fields tells the agent when a push was skipped (see below).docs/design/deal-write-guard.md.update-conversation-fields now returns success: false with isExpected: true (no "Tool Call Failed" alert) when every field's CRM push was skipped for any reason (no connection, ownership mismatch, guard...). Before, it returned a silent success: true; nothing was saved in Cedar either way, the agent just was not told. The partial case reports skippedCrmFields. On the chat/MCP/approval path Cedar is written first, so a skipped push returns savedButNotPushedFields.updateExternalCrm (tRPC) no longer reports success for a skipped or failed workflow. It has no callers under apps or packages.readCrmObjectSettings reads dealSyncFilter through a validating parser (malformed stored value reads as {}) instead of a cast.lib/edit-distance.ts replaces the duplicate Levenshtein in kb/helpers.ts (fuzz-compared against the old implementation, no mismatches).user-tasks.ts) fires the push without recording the result: on a guarded deal the task shows as applied while the CRM is not written. Follow-up.pnpm dlx oxlint@1.78.0 --deny-warnings: exit 0. pnpm deps:check: no violations. check-brand-assets: pass. pnpm --filter @zero/server run types: exit 0 (also after merging staging).deal-write-guard.test.ts (evaluator plus the snapshot SQL against PGlite: newest-row ordering, provider/deal/object scoping, Deal vs Lead collision, never reads dealData.raw, schema-drift check), update-external-crm-workflow.test.ts (driver never called on a block, every skipReason), connection-config.test.ts (storage, wipe guard, key validation and override), updateConversationFieldsTool.test.ts (all four skip/fail combinations, real shouldNotifyToolCallError), the two tRPC partial-failure files and mastra-update-external-crm.test.ts.vitest run src/ and the @zero/mail jest suite (repo rule against unscoped vitest runs, and no mail changes); CI runs them.| Surface | Requests | Errors | p95 | Users | Verdict |
|---|---|---|---|---|---|
| /api/trpc/crm.listConversations | 0 → 3 | 0 → 0 (0%) | not measured → 2105 ms | 0 | Insufficient traffic 3 requests, under the 20 Libra needs |
| /api/trpc/crm.getConversations | 0 → 3 | 0 → 1 (33%) | not measured → 22 ms | 0 | Insufficient traffic 3 requests, under the 20 Libra needs |
| POST /api/trpc/crm.listConversations | 0 → 2 | 0 → 0 (0%) | not measured → 2105 ms | 0 | Insufficient traffic 2 requests, under the 20 Libra needs |
| POST /api/trpc/crm.getConversations | 0 → 2 | 0 → 1 (50%) | not measured → 22 ms | 0 | Insufficient traffic 2 requests, under the 20 Libra needs |
| POST /api/trpc/mastra.chatWarm | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/mastra.chatStream | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/crm.searchConversationsMinimal | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| POST /api/trpc/crm.searchConversationsMinimal | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| crm.searchConversationsMinimal | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| trpc.crm.searchConversationsMinimal | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/mastra.chatWarm | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |
| /api/trpc/crm.loadCalendarConversationsBatch | 0 → 0 | 0 → 0 (0%) | not measured | 0 | No traffic No requests recorded since this deploy. |