# Persistent operation notifications

Updated: 2026-08-02

`UserNotifications` provides durable, owner-scoped visibility for terminal `UserOperations`. It is not an email outbox: no SMTP or provider delivery is triggered by this record.

Each operation may have one current notification, enforced by a unique nullable `OperationId`. Success, permanent failure and cancellation update the operation and insert a generic notification in one relational database transaction. Retryable failures do not notify. Manually retrying a failed or cancelled operation removes its previous terminal notification so its next terminal outcome can create one. Duplicate terminal calls make no change.

Notification text never includes operation inputs, provider diagnostics, failure messages, CV/email/job content or result data. Read and dismiss mutations use the normal owner query filter; dismissed notifications are excluded from lists and unread counts. Authenticated APIs expose list/unread/read/dismiss under `/api/notifications`.

The frontend `/operations` page polls only while mounted, shows loading/empty/error/progress/cancellation/retry states and dispatches a local refresh event after notification mutations. The application shell polls only the unread count every 60 seconds; the reminders badge remains separate. The bell links to `/operations` and has an accessible name.

Migration `20260802225941_AddUserNotifications` is EF-owned and absent from startup reconciliation. SQLite uses native scaffolded types; MariaDB uses bounded `char`/`varchar` and `datetime(6)` columns with a foreign key that sets `OperationId` null if operation history is deleted.

Rollback requires stopping producers/workers, retaining any required notification evidence elsewhere, then applying the migration `Down`. An older application can run with this additive table still present, which is the safer application rollback. Dropping the table loses notification state.
