Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(drafter): notify post-meeting drafts through notify-user, not hardcoded

merged#2664CedarCopilot

CedarCopilot wants to merge 6 commits into staging from feat/notify-user-post-meeting-drafts

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 2, 2026, 11:18 AM
  2. Sep 2, 2026, 12:47 PM
  3. Merged
    Sep 2, 2026, 2:07 PM
  4. Live on prod
    Sep 2, 2026, 2:07 PM
  5. Observed 2 hours
    Sep 2, 2026, 2:07 PM
  6. Unobserved

    Live on prod, no production signal yet

    Sep 2, 2026, 2:07 PM
  7. Pipelines steady after this deploy
    Sep 2, 2026, 2:07 PM

Behaviors Libra is checking

CRM integration behavior from account-setup/<id>.ts, tools/applyConfigChangeTool.ts keeps working in prod.Not checked
prod
Email workflow behavior from components/UserSettingsEditor.tsx, playground/notification-test.tsx, notifications/page.tsx keeps working in prod.Not checked
prod
Slack integration behavior from event-execution/onEventExecutionDraftSlackTool.ts, notifications/user-slack-notifier.ts keeps working in prod.Not checked
prod

Libra has verdicts on 0 of 3 tracked behaviors on prod; 3 are still being checked. Libra checks hourly for 3 days after each deploy.

What and why

Post-meeting "your draft is ready" was the one notification in Cedar a user could not control. draft-email and draft-slack fired it themselves, programmatically, through userNotificationRegistry.post_meeting_draft_ready , so it could not be moved to another channel, reworded, or switched off, and it ignored the playbook entirely. Every other agent notifies through notify-user, driven by prose the user owns.

This migrates it onto notify-user, and deletes the machinery that existed only to serve it.

The pieces

LayerChange
lib/cedar-routes.tsNew ExternalRoute enum + externalUrl() for absolute links (cedarUrl prepends VITE_PUBLIC_APP_URL, so it can't build them). The patterns and the #all/ vs #drafts/ rule are taught in generateCedarLinksBlock(), which every preamble carries.
agents/on-event-execution-agent.tsGrants notify-user. Corrects the prompt, which claimed the draft tools fire a DM and banned notify-user outright.
orchestrator-dispatch-tools.tsAppends a <notification_policy> fence , inside the drafter?.instructions branch.
agent-defaults/drafter.tsA "Telling the user the recap is waiting" section the user can edit or delete.
DeletionsLAYER 6.5, LAYER 9, and everything only they reached.

Link URLs are model-authored , no resolver

Settled before this PR: in the 30 days to 2026-08-29 the model wrote 6,341 link-button URLs, including 522 Salesforce, 161 HubSpot, 19 Notion and 3 app.slack.com , hosts no registry could have enumerated. So the leverage is in teaching the shape once, centrally, not in plumbing resolved URLs through requestContext.

The verified URL forms

All four were tested by clicking in production. Do not "tidy" them.

  • Gmail thread with sent/received mail , https://mail.google.com/mail/u/0/#all/{gmailThreadId}
  • Gmail draft alone in its thread , https://mail.google.com/mail/u/0/#drafts/{gmailThreadId} (#all/ finds nothing; an unsent draft isn't in All Mail)
  • Slack channel or DM , https://slack.com/app_redirect?team={teamId}&channel={channelId} (chosen over app.slack.com/client and slack:// because it prompts to open the desktop app)
  • Cedar thread , the existing CedarRoute.EmailThread

u/0 means "first signed-in account". The u/{email} form Gmail shows in the address bar 404s when pasted , the enum member carries a comment saying so, and a test asserts {email} never appears.

The invariant that had to survive

orchestrator-dispatch-tools.ts documents: THE DOCUMENT'S PRESENCE IS THE SWITCH , an account with no drafter.md gets a byte-identical pre-drafter prompt. That is the whole safety argument during a per-account backfill, so the <notification_policy> block is built inside the same if (drafter?.instructions) branch as <drafter_instructions>. No document → no block → no grant, and the executor's own prompt says "with no such block, do not notify at all".

drafter-executor-prompt.test.ts already pinned that with an exact string comparison; this adds an explicit not.toContain('<notification_policy>') on the no-document path and a placement assertion on the document path.

Deleted, and confirmed unreachable

Each was grepped across apps/server and apps/mail before removal:

  • LAYER 6.5 (onEventExecutionDraftEmailTool.ts) and LAYER 9 (onEventExecutionDraftSlackTool.ts) , the two post_meeting_draft_ready call sites. LAYER 6.5 held savedDraft.threadId!, a forbidden non-null assertion that goes with it.
  • user-notification-registry.ts (whole file) , post_meeting_draft_ready was its only entry.
  • notification-dispatcher.ts (whole file) and DispatchPayload , the registry's send was their only caller. Nothing imports the module now. loops-client.ts is still used by four other call sites and stays.
  • PostMeetingDraftReadyNotification, formatPostMeetingDraftMessage, `formatMeetingT
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • .claude/skills/account-setup-from-transcript/SKILL.mdno production surface mapped
  • .cursor/skills/account-setup-from-transcript/SKILL.mdno production surface mapped
  • AGENTS.mdno production surface mapped
  • apps/mail/app/(routes)/playground/components/UserSettingsEditor.tsxno production surface mapped
  • apps/mail/app/(routes)/playground/notification-test.tsxno production surface mapped
  • apps/mail/app/(routes)/settings/notifications/page.tsxno production surface mapped
  • apps/server/.claude/skills/sherlock/SHERLOCK_SYSTEM_KNOWLEDGE.mdno production surface mapped
  • apps/server/src/db/migrations/scripts/account-setup/__tests__/phase9-notifications.test.tsno production surface mapped
  • apps/server/src/db/migrations/scripts/account-setup/phase9-notifications.tsno production surface mapped
  • apps/server/src/lib/__tests__/cedar-routes.test.tsno production surface mapped
  • apps/server/src/lib/cedar-routes.tsno production surface mapped
  • apps/server/src/lib/schemas.tsno production surface mapped
  • apps/server/src/mastra/agents/on-event-execution-agent.tsno production surface mapped
  • apps/server/src/mastra/skills/account-config/tools/applyConfigChangeTool.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/__tests__/draft-tools-sibling-guard.test.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/__tests__/drafter-executor-prompt.test.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/onEventExecutionDraftEmailTool.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/onEventExecutionDraftSlackTool.tsno production surface mapped
  • apps/server/src/mastra/tools/event-execution/orchestrator-dispatch-tools.tsno production surface mapped
  • apps/server/src/services/notifications/__tests__/post-meeting-draft-recipient-warning.test.tsno production surface mapped
  • apps/server/src/services/notifications/__tests__/user-whatsapp-notifier.test.tsno production surface mapped
  • apps/server/src/services/notifications/notification-dispatcher.tsno production surface mapped
  • apps/server/src/services/notifications/types.tsno production surface mapped
  • apps/server/src/services/notifications/user-imessage-notifier.tsno production surface mapped
  • apps/server/src/services/notifications/user-notification-registry.tsno production surface mapped
  • apps/server/src/services/notifications/user-slack-notifier.tsno production surface mapped
  • apps/server/src/services/notifications/user-whatsapp-notifier.tsno production surface mapped
  • apps/server/src/services/playbook/agent-defaults/__tests__/agent-defaults.test.tsno production surface mapped
  • apps/server/src/services/playbook/agent-defaults/drafter.tsno production surface mapped
  • apps/server/src/services/users/__tests__/user-settings.test.tsno production surface mapped
  • apps/server/src/services/users/user-settings.tsno production surface mapped
  • apps/server/src/trpc/routes/admin-notifications.tsno production surface mapped