# NotificationCategory

## Description

NotificationCategory groups individual eventTypes into user-visible buckets — for example "PO updates", "RFQ updates", "Task assignments", "Announcements", "Compliance documents", "Onboarding", "Supplier lifecycle". The category is the unit at which users express opt-in/opt-out preferences (via NotificationPreference); the dispatcher resolves an inbound `eventType` to its category through EventCategoryBinding and then evaluates preferences on the resolved category.

Each category carries an `optOutAllowed` boolean that distinguishes optional categories (broadcasts, reminders, digests) from transactional categories (PO confirmation, invoice approval, supplier suspension) that compliance and operational continuity require to always reach the recipient. When `optOutAllowed = false`, NotificationPreference commands targeting the category are rejected at the command boundary so no opt-out row can be persisted, and the dispatcher unconditionally bypasses the preference filter for events resolving to that category.

The catalog is seeded by the host application (see the Event Catalog seed contract in the notification-delivery feature) and is not editable at runtime — adding a new category is a host-app seed change, not a runtime mutation. End users have no path to create, rename, or delete categories.

## Domain Model Definitions

### Model type

Standard

### Command Definitions

- No runtime commands — NotificationCategory rows are seeded by the host application and are not mutated through the command surface

### Query Definitions

- [listNotificationCategories](../query/ListNotificationCategories.md) - List the seeded category catalog so user-preference UIs can render the available buckets

### Models

- NotificationCategory

### Invariants

- `categoryId` is the primary key and is unique across the catalog
- `displayName` must be non-blank
- `optOutAllowed = false` causes NotificationPreference commands targeting the category to be rejected at the command boundary with `CATEGORY_NOT_OPT_OUT_ABLE`
- `optOutAllowed = false` causes the dispatcher to bypass the preference filter and deliver the event on every default channel regardless of any stale rows that may exist
- New categories added to the seed catalog after launch leave existing users implicitly opted in — no row in NotificationPreference for a category is treated by the dispatcher as `allowed = true`
- Categories cannot be created, renamed, or deleted at runtime; the catalog is owned by the seed surface

### Relationships

- **Referenced by NotificationPreference as categoryId**: per-user opt-in/opt-out rows are keyed by `(userId, categoryId, channelId)`
- **Referenced by EventCategoryBinding as categoryId**: the seeded `eventType → categoryId` map binds each emitter event to exactly one category
