Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(settings): one resolver for user settings, and writers that put keys where they are read

merged#2568CedarCopilot

CedarCopilot wants to merge 14 commits into staging from fix/user-settings-resolution-registry

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 24, 2026, 11:08 AM
  2. Aug 24, 2026, 5:58 PM
  3. Merged
    Aug 24, 2026, 6:14 PM
  4. Live on prod
    Aug 24, 2026, 6:14 PM
  5. Observed 40 hours
    Aug 24, 2026, 6:14 PM
  6. Pipelines steady after this deploy
    Aug 24, 2026, 6:14 PM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 26, 2026, 10:03 AM

Behaviors Libra is checking

AOP context behavior from aop/automations.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback saw 4,111 success-shaped log lines matching pipeline, analytics, quota, aop, but no tied operation was present, so Libra is not calling this working.

prod, checked Aug 26, 2026, 1:03 AM
Authentication and access behavior from lib/auth.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback saw 36 setup or missing-connection failure log lines matching auth, login, session, token, but that does not prove the deployed behavior is broken for connected users.

prod, checked Aug 26, 2026, 2:02 AM
Background jobs behavior from cron/process-agenda-emails.ts, worker/queue-handler.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 15 recent prod failure log lines for mirror.upsertEmailThread failed (Failed query: insert into \), but the same failure had 2 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR intent without a post-deploy increase or.

prod, checked Aug 26, 2026, 3:03 AM
Calendar sync behavior from crm/calendar-events.ts, workflows/calendar-sync.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [MEETING_NOTES_WEBHOOK] Error processing webhook (AskElephant webhook payload must contain at least one attendee with a valid email address), but this domain-wide failure family is not tied to this intent.

prod, checked Aug 26, 2026, 4:03 AM
Chat workflow behavior from context-formatting/conversation-context.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 15 recent prod failure log lines for mirror.upsertEmailThread failed (Failed query: insert into \), but the same failure had 2 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR intent without a post-deploy increase or.

prod, checked Aug 26, 2026, 5:03 AM
CRM integration behavior from account-setup/phase5-email.ts, account-setup/<id>.ts, account-setup/types.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 2 recent prod failure log lines for [executeExternalCrm] Deal owner reassignment failed (non-critical) (Target user is already the owner), but the same failure had 9 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR.

prod, checked Aug 26, 2026, 6:03 AM

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

What was wrong

Trey at Pax reported getting email notifications from Cedar. 38 users across ~20 orgs were mailed their Slack notifications over two days; the volume exhausted the Resend daily quota, at which point notifications failed outright for 23 of them.

user_settings stores preferences in two generations: the legacy settings blob and the dedicated mail_settings / notification_settings / agent_settings columns. The rule is "the dedicated column wins; where it is empty, the blob answers". Nothing enforced it, so it was implemented ten different places, four different ways , every UI path tolerant, every runtime path not. Trey's settings page said "Slack notifications: enabled" the whole time his DMs went to his inbox.

Chasing it turned up three separate writers putting categorized keys somewhere nothing reads:

  1. Account setup Phase 9B , raw SQL in SKILL.md writing slackNotifications to the blob. Provably a source: it also wrote dailyFollowupDigest/dailyCrmActivityDigest, keys no schema reads, and that fingerprint is on 78 users.
  2. Signup , insertUserSettings(defaultUserSettings) wrote the blob only, so every new user landed in the broken shape. Five Payroll Integrations users were created in that state hours after the first reconcile run, months after the dual-write shipped.
  3. The admin toggles , togglePushDrafts and toggleLabelSync read-modify-wrote the blob and returned enabled to the UI while the runtime read agent_settings with no fallback. This is where the four "push-drafts on in admin, off in reality" accounts came from.

What changed

One rule, one place. user-setting-specs.ts is the single declaration of all 16 categorized keys; CATEGORIZED_KEYS, extractCategorizedSettings and the three input schemas derive from it or are locked to it. resolve-user-settings.ts is the rule, and nested notification blocks now merge sub-key by sub-key (40 rows had a thin dedicated block silently erasing the blob's digestTime/deliveryMethod).

Writers put keys where they are read. Account setup Phase 9B is a script, not SQL in markdown. insertUserSettings seeds all four columns in one atomic insert. All four admin toggles are one line through the single writer, deleting two hand-rolled upserts.

Renames are declared, not migrated. agendaEmaildailyRecap lives on the spec, so it is honoured on every read and every reconcile run, reaching rows no one-shot UPDATE ever did.

The backfill is re-runnable. reconcile-categorized-settings.ts, dry-run by default, verified idempotent.

extractCategorizedSettings now omits absent keys instead of assigning undefined, deleting six copies of Object.entries(x).filter(([, v]) => v !== undefined) across three files.

Guards , the actual point

  • Exhaustive Record: a new setupRequired key fails the build in phase9-notifications.ts. Verified , adding one produces TS2741.
  • categorized-key-access.test.ts: fails when any file reads a categorized key off the blob, or when the seed / toggles stop writing the dedicated columns. It found two live bugs while being written (admin-notifications reporting the blob only; user-context resolving the digest timezone off the blob only).
  • SUBAGENT_ROLLOUT: adding a system-agent slug will not compile until seed/backfill/always-on are answered, and a source guard checks the answers against what seed-playbook.ts does.

Deliberate behaviour changes , please review

  1. process-agenda-emails.test.ts , the test asserting the retired dailyRecap must not stand in for agendaEmail is inverted. Its stated concern was a fallback letting the switch-to-Slack endpoint patch a key that is not the one read; the resolver reads new-key-first, so an explicit agendaEmail: false still wins. Both halves are pinned. The original assumed agenda_email_flag.sql had renamed the flag on every row; it was gated on a key t
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
  • apps/mail/app/(routes)/playground/components/UserSettingsEditor.tsxno production surface mapped
  • apps/mail/app/(routes)/settings/general/page.tsxno production surface mapped
  • apps/mail/app/(routes)/settings/notifications/page.tsxno production surface mapped
  • apps/server/src/cron/__tests__/process-agenda-emails.test.tsno production surface mapped
  • apps/server/src/cron/process-agenda-emails.tsno production surface mapped
  • apps/server/src/cron/process-periodic-slack-channel-sync.tsno production surface mapped
  • apps/server/src/db/migrations/scripts/__tests__/reconcile-categorized-settings.test.tsno 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/phase5-email.tsno production surface mapped
  • apps/server/src/db/migrations/scripts/account-setup/phase9-notifications.tsno production surface mapped
  • apps/server/src/db/migrations/scripts/account-setup/types.tsno production surface mapped
  • apps/server/src/db/migrations/scripts/reconcile-categorized-settings.tsno production surface mapped
  • apps/server/src/lib/auth.tsno production surface mapped
  • apps/server/src/lib/schemas.tsno production surface mapped
  • apps/server/src/lib/server-utils.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/request-context.tsno production surface mapped
  • apps/server/src/mastra/routeHandlers/event-execution/handleExecuteExternalCrm.tsno production surface mapped
  • apps/server/src/mastra/routeHandlers/event-execution/handleExecuteMeeting.tsno production surface mapped
  • apps/server/src/mastra/routeHandlers/event-execution/handleExecuteSyncSlack.tsno production surface mapped
  • apps/server/src/mastra/routeHandlers/event-execution/handleExecuteSyncThread.tsno production surface mapped
  • apps/server/src/mastra/skills/account-config/tools/readConnectionSettingsTool.tsno production surface mapped
  • apps/server/src/mastra/tools/notify/notifyUserTool.tsno production surface mapped
  • apps/server/src/mastra/utils/context-formatting/conversation-context.tsno production surface mapped
  • apps/server/src/mastra/utils/context-formatting/user-context.tsno production surface mapped
  • apps/server/src/services/analytics/draft-analytics.tsno production surface mapped
  • apps/server/src/services/aop/automations.tsno production surface mapped
  • apps/server/src/services/crm/calendar-events.tsno production surface mapped
  • apps/server/src/services/drafting/drafting.tsno production surface mapped
  • apps/server/src/services/mail/labels/labels.tsno production surface mapped
  • apps/server/src/services/notifications/notification-dispatcher.tsno production surface mapped
  • apps/server/src/services/notifications/user-imessage-notifier.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/index.tsno production surface mapped
  • apps/server/src/services/users/USER_SETTINGS_RESOLUTION_DESIGN.mdno production surface mapped
  • apps/server/src/services/users/__tests__/categorized-key-access.test.tsno production surface mapped
  • apps/server/src/services/users/__tests__/resolve-user-settings.test.tsno production surface mapped
  • apps/server/src/services/users/__tests__/user-setting-specs.test.tsno production surface mapped
  • apps/server/src/services/users/resolve-user-settings.tsno production surface mapped
  • apps/server/src/services/users/user-setting-specs.tsno production surface mapped
  • apps/server/src/services/users/user-settings.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/settings-router.test.tsno production surface mapped
  • apps/server/src/trpc/routes/admin-email-analysis.tsno production surface mapped
  • apps/server/src/trpc/routes/admin-notifications.tsno production surface mapped
  • apps/server/src/trpc/routes/admin.tsno production surface mapped
  • apps/server/src/trpc/routes/cedar-admin.tsno production surface mapped
  • apps/server/src/trpc/routes/mail.tsno production surface mapped
  • apps/server/src/trpc/routes/settings.tsno production surface mapped
  • apps/server/src/worker/queue-handler.tsno production surface mapped
  • apps/server/src/workflows/calendar-sync.tsno production surface mapped