CedarCopilot wants to merge 14 commits into staging from fix/user-settings-resolution-registry
Live on prod, no production signal yet
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.
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.
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.
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.
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.
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.
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.
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:
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.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.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.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. agendaEmail ← dailyRecap 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.
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.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 tLibra has not measured any production surfaces for this change yet.