An official website of the United States GovernmentUnclassified
Seal of the Department of the NavyDepartment of the NavyProject ROUNDTABLE Docs
Data model

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

ColumnPrisma typeRequired?DefaultMeaning
idStringYescuid()Notification identifier.
userIdStringYesNoneRecipient.
kindNotificationKindYesNoneCategory used by the bell and settings.
titleStringYesNoneShort alert title.
bodyStringYesNoneHuman-readable details.
linkUrlString?NoNULLOptional in-app destination.
readBooleanYesfalseWhether the recipient marked it read.
createdAtDateTimeYesnow()Creation time.

The user relation enforces recipient ownership. Notification list/read endpoints always filter by the current user's ID.

NotificationKind

ValueMeaning
SUBMISSION_MATCHA submission matched a portfolio or interest profile.
ANNOUNCEMENTA government announcement was published.
CALL_OPENEDA call for technology became available.
ENGAGEMENT_UPDATEAn engagement-related update.
SYSTEMSystem or administrative notice.

NotificationSetting reminder

ColumnPrisma typeRequired?DefaultMeaning
idStringYescuid()Preference identifier.
userIdStringYesNonePreference owner.
kindNotificationKindYesNoneCategory.
enabledBooleanYestrueOn/off switch.
techTagsStringYes""Optional event-tag filter.

The unique pair (userId, kind) lets PUT settings safely upsert. A missing row means enabled by default.

AuditLog

ColumnPrisma typeRequired?DefaultMeaning
idStringYescuid()Audit identifier.
timestampDateTimeYesnow()Time of recorded event.
eventStringYesNoneStable event label such as submission_routed.
userEmailString?NoNULLActor or affected account email when available.
ipString?NoNULLSource IP when the route captures it.
detailString?NoNULLOptional JSON string with operational context.
userAgentString?NoNULLRequest user-agent, truncated to 512 characters. Written by pageview rows.
pathString?NoNULLPathname of the recorded page, query string and fragment excluded.
referrerString?NoNULLPathname portion of document.referrer, sent only on the first view after a full page load. The host is discarded, so an external referrer contributes its path only.

Indexes: timestamp, event, and the composite (event, path) — the last one exists so the traffic summary can group pageviews by path without scanning the whole trail.

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, authentication_rate_limited, authentication_locked, account_locked, 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, password_change_success, password_change_blocked (a change attempted while credential changes are locked), and pageview.

Pageviews on the audit table

pageview rows share AuditLog rather than living in their own table: they are observational records with the same retention, access controls, and append-only semantics as the rest of the trail. They differ from other events in three ways — they are written by the client rather than by a mutating handler, they populate path/referrer/userAgent, and they are the only event read back in aggregate by a UI page.

Because the writer is a client effect, absence of a row is not evidence a page was not viewed: tracking is fire-and-forget, and a blocked request, an offline browser, or a rate-limited client simply produces no row. Treat pageview counts as usage signal, never as an authorization or accountability record — that is what the other event labels are for.

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.

Audit rows are append-only by design: there is no soft-delete flag and no application path that edits or removes a row, so the trail only grows. Pair that with database access controls and a retention policy at the storage layer, which is where immutability is enforced for the long term.