CedarCopilot wants to merge 14 commits into staging from feat/inbox-client-composed-feed
Live on staging, watching, 2 days left
Libra has no production signal for this change on staging yet. Libra checks hourly for 3 days after each deploy.
Started as Zach's bug , an email sent from Cedar that never reached Gmail , and grew into the send/ingest path, the telemetry needed to diagnose it next time, and a full review of everything else on this branch.
The original bug, four root causes:
.catch(() => {})), so a failed send left the optimistic timeline asserting an email that was never sent.endsWith('/mail.send') while the client uses httpBatchLink, so it stopped protecting sends the moment one shared a batch.Threading: replies composed from a surface that only knew the threadId shipped no In-Reply-To/References, so Gmail opened a new conversation on the recipient's side , 200 replies over 30 days, 17 users. The driver now derives the chain from the thread rather than trusting whichever client surface composed the message.
Observability: a client-minted correlation id on every tRPC request, recorded server-side on the span and in the structured-log context, plus a browser telemetry relay. Aborts and network failures are captured at 100% , those leave no server row at all, which is what made this undiagnosable for a day.
Data: 22 Gmail-confirmed phantom crm_events deleted after per-message verification against each owning mailbox (one BOTH-EXIST pair deliberately spared). Two analytics_draft_actions rows were repointed off the deleted phantoms , they had no FK, so nothing dangled, but attribution-gap.ts matches drafts to sends by message_id and both real Cedar-drafted sends were being reported as unattributed.
A review of the whole branch (ccd947369) turned up, and this fixes:
normalizeAsciiHeaders unwrapped encoded-words decoding to \x00-\x7F, which includes CR/LF. Verified against the real codec: a subject of Hello\r\nBcc: <email> came back out as a live Bcc: header, which messages.send honours. Subjects arrive unvalidated from mail.send and agent-composed ones derive from inbound mail, so it was reachable by prompt injection.unipileAccountId from the request body and never checked it belonged to the caller's org; most orgs fall back to a shared Unipile credential that answers for any seat.add_rows storing the same hybrid set refuses, a merged heading round-tripping through Excel as data (hard-failing re-import of an unmodified Cedar export), and shift-extend onto a heading widening the selection to columns the user never picked.MM/DD/YYYY) threw inside the comparator, taking down the grid and the read verb.kind/name/status bypassed the server content denylist; the plain-text MIME part was derived post-tracking so it spelled out the redirector the HTML hides; table-cli create could UPSERT an empty table over a populated path; post-engagement could spend a seat's whole daily profile_view budget in one agent call.Four findings are design decisions rather than defects, flagged rather than changed:
scope-map.ts:252 , a docs:read-only MCP caller can author/overwrite subagent documents Cedar later executes under the user's identity (but cannot delete them , that still needs playbook:write).provider-credentials.ts:79 , the one-level flattLibra has not measured any production surfaces for this change yet.