Notifications and audit
User alerts, preferences, and accountability records
Notifications are user-facing work queues; audit logs are accountability records. A notification tells someone something happened. An audit log records that the application accepted an authentication, authorization, publishing, routing, or administrative action.
Notification
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Notification identifier. |
userId | String | Yes | None | Recipient. |
kind | NotificationKind | Yes | None | Category used by the bell and settings. |
title | String | Yes | None | Short alert title. |
body | String | Yes | None | Human-readable details. |
linkUrl | String? | No | NULL | Optional in-app destination. |
read | Boolean | Yes | false | Whether the recipient marked it read. |
createdAt | DateTime | Yes | now() | Creation time. |
The user relation enforces recipient ownership. Notification list/read endpoints always filter by the current user's ID.
NotificationKind
| Value | Meaning |
|---|---|
SUBMISSION_MATCH | A submission matched a portfolio or interest profile. |
ANNOUNCEMENT | A government announcement was published. |
CALL_OPENED | A call for technology became available. |
ENGAGEMENT_UPDATE | An engagement-related update. |
SYSTEM | System or administrative notice. |
NotificationSetting reminder
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Preference identifier. |
userId | String | Yes | None | Preference owner. |
kind | NotificationKind | Yes | None | Category. |
enabled | Boolean | Yes | true | On/off switch. |
techTags | String | Yes | "" | Optional event-tag filter. |
The unique pair (userId, kind) lets PUT settings safely upsert. A missing row means enabled by default.
AuditLog
| Column | Prisma type | Required? | Default | Meaning |
|---|---|---|---|---|
id | String | Yes | cuid() | Audit identifier. |
timestamp | DateTime | Yes | now() | Time of recorded event. |
event | String | Yes | None | Stable event label such as submission_routed. |
userEmail | String? | No | NULL | Actor or affected account email when available. |
ip | String? | No | NULL | Source IP when the route captures it. |
detail | String? | No | NULL | Optional JSON string with operational context. |
Audit logs intentionally do not use an enum: new operational events can be added without a migration. Current event labels include authentication_success, authentication_failure, authorization_failure, registration_rate_limited, registration_conflict, industry_registration, org_profile_viewed, org_profile_update, submission_created, submissions_viewed, submission_file_downloaded, submission_routed, submissions_rerouted, search_performed, notification_settings_updated, notifications_mark_read, announcement_published, call_opened, call_status_change, engagement_created, engagement_updated, engagement_deleted, engagement_note_created, org_dialogue_note_created, org_status_change, admin_commands_viewed, admin_command_viewed, admin_command_create, admin_command_update, admin_command_delete, admin_command_reparent, admin_users_viewed, admin_user_create, admin_user_update, admin_user_role_change, export_csv, export_all, export_ics, password_change_rate_limited, password_change_failure, and password_change_success.
Notification flow
notifyUser, notifyUserForTags, notifyGovByTags, and notifyAllIndustry centralize fan-out policy. They create database rows rather than sending external email. This makes the in-app bell deterministic and auditable.
Why audit is not a notification
An audit record has an actor email, optional IP, event label, and operational detail. It is intended for reviewers and operators. A notification has a recipient, title, body, link, read state, and category. It is intended for a user's work queue. A successful action may produce both, only one, or neither depending on route purpose.
Audit detail is JSON serialized text in the current schema. Consumers should parse it defensively because event types do not share one fixed shape. The event label is the stable first filter; detail is supplementary context such as IDs, previous/new parents, match counts, or notification counts.
The schema has no soft-delete flag on audit rows. Application code creates rows but should not treat them as a complete tamper-proof log without database access controls and retention policy.