CedarCopilot wants to merge 3 commits into staging from feat/custom-field-types-json-timestamp
Live on prod, no production signal yet
Strict CloudWatch fallback found 91 prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Step updateOpportunityField failed: HTTP 400: [{\), but this domain-wide failure family is not tied to this intent.
Strict CloudWatch fallback saw 677 success-shaped log lines matching pipeline, analytics, quota, aop, but no tied operation was present, so Libra is not calling this working.
Strict CloudWatch fallback found 16 recent prod failure log lines for mirror.upsertEmailThread failed (Failed query: insert into \), but the same failure had 8 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR intent without a post-deploy increase or.
Strict CloudWatch fallback saw 381 success-shaped log lines matching pipeline, analytics, quota, aop, but no tied operation was present, so Libra is not calling this working.
Strict CloudWatch fallback found 63 prod failure log lines for mirror.upsertEmailThread failed (Failed query: insert into \), but this domain-wide failure family is not tied to this intent.
Strict CloudWatch fallback saw 476 success-shaped log lines matching crm, hubspot, salesforce, deal, deals, but no tied operation was present, so Libra is not calling this working.
Libra has verdicts on 0 of 3 tracked behaviors on prod; 3 are still being checked. Libra has 1 low-confidence degraded verdict it is still confirming. Libra checks hourly for 3 days after each deploy.
Adds a new json custom field type , a Cedar conversation/CRM custom field whose value is a JSON object, written by agents as partial patches that deep-merge onto the stored value. Motivating use case: Pirros's HubSpot deal brief is a single structured-JSON property (ae_data_brief , reasonToBuy, mainObjection, coiValue, incentive, per-contact reads, engagements). The agent fills the case keys while a rep owns the people keys; a partial write must never clobber the rep's sub-keys.
Design doc: docs/design/custom-field-types-json-timestamp.md.
A
timestamptype was scoped but dropped , the existingdatetype already stores full time (toISOString(),datetimedisplay), so it's redundant.
A json field's incoming value is a partial patch. It is deep-merged onto the stored object once, up front, before anything downstream sees it (updateConversationFieldsTool §4.5), so both the external-CRM push and the Cedar persist receive the whole merged object , never the partial. A rejected merge (corrupt stored JSON, non-object root, over-size, invalid patch) is dropped: not pushed, not persisted, not returned , and surfaced to the agent as a failed field.
services/crm/json-field-merge.ts , pure deepMergeJson (immutable; object-over-object recurses so sibling keys like reads are preserved; undefined ignored, null clears, arrays replace, scalars win) + resolveJsonFieldPatch (read+merge+validate) + applyJsonFieldPatch (resolve+persist) + resolveJsonFieldPatchesInList (batch resolve for the write path). Typed rejection reasons; no throw escapes; io-error distinguishes infra failure from corrupt-existing.content text column , no DB migration.ae_data_brief is a text property holding stringified JSON).Invariant to preserve: partial-patch json writes must go through the tool's §4.5 resolver. Any other caller writing a json field must send the whole merged object (or call resolveJsonFieldPatch). Today the only partial-patch writer is the agent tool; the frontend editor writes whole objects.
services/crm/__tests__/json-field-merge.test.ts , 29 tests: the deep-merge matrix (incl. sibling-preservation / the deal-brief never-clobber case), the Cedar-side guards (corrupt/non-object/too-large reject without overwriting), io-error vs corrupt-existing for read+persist failures, and the batch resolver (a json field resolves to the merged object , the exact value pushed to the CRM , and a rejected field is dropped from the resolved set).
Went through thermo-review. The first pass caught a blocking data-loss bug , the merge originally protected only the Cedar copy while the CRM push sent the pre-merge partial (clobbering reads in HubSpot on every agent write). Fixed by moving the merge ahead of the push (§4.5 above). Gates: @zero/server types ✓, @zero/mail types ✓, deps:check ✓ (no violations), json tests 29/29 ✓.
🤖 Generated with Claude Code
The PR adds JSON custom fields whose agent-written partial objects are resolved into complete deep-merged values before proposal, streaming, external CRM synchronization, and Cedar persistence.
The PR appears safe to merge.
No blocking failure remains.
| Filename | Overview |
|---|---|
| apps/server/src/services/crm/json-field-merge.t |
Libra has not measured any production surfaces for this change yet.