{"record":{"id":"b03e155fcc875665","repo":"koala73/worldmonitor","slug":"incompatible-delivery-b03e15","errorCode":"INCOMPATIBLE_DELIVERY","errorMessage":"Real-time delivery requires High or Critical sensitivity.","messagePattern":"Real-time delivery requires High or Critical sensitivity\\.","errorType":"validation","errorClass":"IncompatibleDeliveryError","httpStatus":400,"severity":"warning","filePath":"src/services/notification-channels.ts","lineNumber":463,"sourceCode":"  sensitivity?: Sensitivity;\n  channels?: ChannelType[];\n  aiDigestEnabled?: boolean;\n  digestMode?: DigestMode;\n  digestHour?: number;\n  digestTimezone?: string;\n  countries?: string[];\n  tickers?: string[];\n}, expectedUserId?: string, signal?: AbortSignal): Promise<void> {\n  const res = await authFetch('/api/notification-channels', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ action: 'set-notification-config', ...args }),\n  }, expectedUserId, signal);\n  if (res.ok) return;\n  let body: { error?: string; message?: string } = {};\n  try { body = await res.json(); } catch { /* keep default */ }\n  if (res.status === 400 && body.error === 'INCOMPATIBLE_DELIVERY') {\n    throw new IncompatibleDeliveryError(\n      body.message ?? 'Real-time delivery requires High or Critical sensitivity.',\n    );\n  }\n  throw new Error(`set notification config: ${res.status}`);\n}\n","sourceCodeStart":445,"sourceCodeEnd":469,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/services/notification-channels.ts#L445-L469","documentation":"Default message for IncompatibleDeliveryError, thrown by setNotificationConfig() in src/services/notification-channels.ts when POST /api/notification-channels (action=set-notification-config) answers 400 with body.error === 'INCOMPATIBLE_DELIVERY'. The server's cross-field validator rejects digestMode 'realtime' paired with sensitivity 'all' — real-time delivery is only allowed with High or Critical sensitivity (plan forbid-realtime-all-events.md §1f). The dedicated error class lets the settings UI render inline helper text instead of a generic failure; the exact server message overrides this default when present.","triggerScenarios":"Calling setNotificationConfig({ digestMode: 'realtime', sensitivity: 'all', ... }) (or leaving sensitivity at 'all' while switching digestMode to 'realtime'); a UI flow that saves digest mode and sensitivity in separate steps can also trip the validator transiently; any API consumer that assumes the (realtime, all) pair is accepted.","commonSituations":"Users switching from daily digest to real-time without raising sensitivity first; stale clients after the validation rule shipped, still sending the previously-legal combination; form state bugs that reset sensitivity to 'all' silently.","solutions":["Set sensitivity to 'high' or 'critical' in the same call that sets digestMode: 'realtime' — the endpoint applies both atomically.","Pre-validate the pair client-side before POSTing (mirror the rule) so the user gets feedback before submit.","Catch IncompatibleDeliveryError specifically (instanceof) and show the server's body.message inline rather than a generic error.","If migrating legacy configs, upgrade sensitivity in the same payload when switching to realtime."],"exampleFix":"// before\nawait setNotificationConfig({ variant: 'global', digestMode: 'realtime', sensitivity: 'all' });\n\n// after\nconst sensitivity = args.digestMode === 'realtime' && args.sensitivity === 'all'\n  ? 'high'\n  : args.sensitivity;\nawait setNotificationConfig({ ...args, sensitivity });","handlingStrategy":"validation","validationCode":"const REALTIME_SENSITIVITY = new Set(['high', 'critical']);\n\nif (config.digestMode === 'realtime' && !REALTIME_SENSITIVITY.has(config.sensitivity)) {\n  showInlineError('Real-time delivery requires High or Critical sensitivity.');\n  return;\n}\nawait setNotificationConfig(config);","typeGuard":"import { IncompatibleDeliveryError } from '@/services/notification-channels';\n\nfunction isIncompatibleDelivery(err: unknown): err is IncompatibleDeliveryError {\n  return err instanceof IncompatibleDeliveryError;\n}","tryCatchPattern":"try {\n  await setNotificationConfig(args);\n} catch (err) {\n  if (isIncompatibleDelivery(err)) {\n    showInlineHelper(err.message); // render guidance, keep the form open\n    return;\n  }\n  throw err;\n}","preventionTips":["Mirror the (digestMode, sensitivity) compatibility rule client-side so invalid pairs never POST.","Always send digestMode and sensitivity together in one set-notification-config call — the endpoint validates them atomically.","Catch IncompatibleDeliveryError by instanceof and render its message inline rather than a toast."],"tags":["api-validation","notification-channels","business-rule","http-400"],"backgroundTag":"api-validation-rejected","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}