CedarCopilot wants to merge 0 commits into staging from feat/slack-style-reactions
Live on prod, no production signal yet
OTEL fallback found no prod spans matching AOP analysis since deploy.
OTEL fallback found no prod spans matching AOP analysis since deploy.
OTEL fallback found no prod spans matching CardListCanvasView / `useCanvasConversations` since deploy.
OTEL fallback found no prod spans matching ChatRenderer / ChatBubbles since deploy.
OTEL fallback found no prod spans matching global shortcut `newChat` / desktop File menu since deploy.
OTEL fallback found no prod spans matching GET mail.getThread / mail inbox thread rendering since deploy.
Libra has verdicts on 0 of 12 tracked behaviors on prod; 12 are still being checked. Libra checks hourly for 3 days after each deploy.
Four independent workstreams that accumulated on this branch after #2513 merged, plus a review pass over all of them and the fixes it turned up.
The classifier used to be told to self-apply a 0.75 threshold it was never asked to emit, while the context builder simultaneously told it "select EXACTLY ONE". The imperative won, so coin flips came back looking confident , median 190 hours from a wrong assignment to a human noticing.
The model now reports a calibrated confidence and every alternative it weighed; code applies the policy (HIGH ≥ 0.75 assign silently, MEDIUM ≥ 0.5 assign and ask, LOW do not assign and let the next event re-classify). A stated uncertaintyReason demotes an otherwise-HIGH answer, because the model recognises ambiguity reliably and prices it badly.
On MEDIUM , or a close call, or a flip-flopping conversation , Cedar can DM the user a "here's what I did, here's what I almost did" ping whose buttons are the AOPs it actually considered. Two gates: it only fires if a wrong pick would do something (some candidate has AI execution on), and at most once per conversation per 30 days.
This is default-OFF behind cedar-aop-ambiguity-ping. The decision is computed and logged regardless, so ping volume is measurable before any customer receives a DM. A dry-run harness reports what would have been sent, over live data, without sending.
Headless throughout: crmAdmin.classify → crm-admin classify <id> --explain runs the real classifier read-only. Wiki at apps/server/docs/wiki/aop-classification.md.
Five heavy libraries were reachable from the eagerly-registered root store, so every route paid for them , /login and /calendar included:
@xyflow/react via outboundFlowSlice → the appliers moved to FlowCanvas, which already has React Flow loadedchrono-node + fuse.js via one pure date helper in the crm/utils barrel → split into date-format.tsDOMPurify via email-utils → cleanHtml split into html-sanitize.tscedar-os barrel's useCedarEditor re-export → import the component directlyreact-markdown via messagesSlice seeding the message-renderer registry in its slice initializer → the built-ins are now reached through resolveMessageRenderer from the already-lazy chat chunkSentry Session Replay (rrweb, ~136 KB) attaches on an idle frame behind its own dynamic-import module , importing @sentry/react dynamically from lib/sentry.ts does nothing, since that file already imports it statically and Rollup has nothing to split.
Measured on the real bundle, index.html's modulepreload set: 2.43 MB / 77 chunks → 1.82 MB / 64 chunks. All five libraries are absent from it.
Also: a preconnect to the API origin, and the FCP-gaming skeleton is reverted , it moved the metric ~2 s without making the page usable any sooner.
The InMail check read Unipile's folder for "INMAIL". Measured over 400 live chats, folder is ["INBOX","INBOX_LINKEDIN_CLASSIC"] on 399 of them regardless of origin , so the check never once fired, linkedin_chats held zero rows with chat_type='inmail', and 51 of those 400 cold InMail / sponsored / offer chats sat in the unibox as ordinary DMs.
The real markers are content_type on the chat and message_type on the message. Quarantine now covers all three kinds on every surface , unless the seat has engaged, since answering a cold reach is what makes it a real conversation.
Separately, the LinkedIn ingest dual-wrote the shared container but never its participants, so every chat created after the 2026-08-09 backfill showed "LinkedIn member" with no avatar. 250 of 545 containers had zero participants; a seat onboarded after that date saw the placeholder on its entire inbox. Ingest-side write plus a catch-up migration.
Libra has not measured any production surfaces for this change yet.