# CreateNotificationChannel

## Permission Scope

channelRegistry

## Overview

createNotificationChannel inserts a row into the NotificationChannel registry so the dispatcher can target a new delivery medium. The command is invoked at module bootstrap time (seed) and by tenant administrators when the application layer wires a new channel adapter (e.g. enabling EMAIL once the SendGrid credentials are configured). The registry row carries the `channelId` enum value, the `kind` topology (`PERSONAL` | `DESTINATION`) derived from `channelId` at insert time, an admin-facing `displayName`, a `capabilities` object describing what the underlying adapter supports (`supportsHtmlBody`, `supportsAttachments`, `supportsRichActions`), and an `enabled` kill-switch defaulting to `true`. The command does not validate that an adapter implementation is wired — that binding lives at the application composition layer; the registry only declares which channels the dispatcher should consider.

## Business Rules

- `channelId` (enum: `IN_APP`, `EMAIL`, `SMS`, `PUSH`, `SLACK`, `TEAMS`) is required
- `displayName` is required and must be non-blank after trimming
- `capabilities` is required and must include `supportsHtmlBody`, `supportsAttachments`, and `supportsRichActions` booleans
- `enabled` is optional and defaults to `true`
- A NotificationChannel row with the same `channelId` must not already exist
- `kind` is **not** a caller input — it is derived from `channelId` at insert time per the fixed mapping (`IN_APP`/`EMAIL`/`SMS`/`PUSH` → `PERSONAL`; `SLACK`/`TEAMS` → `DESTINATION`) and persisted on the row
- Reserved `channelId` values (SMS, PUSH, TEAMS) may be registered ahead of adapter wiring; the dispatcher will skip them at runtime if no adapter is present. SLACK is an active DESTINATION channel whose concrete adapter is bundled as the opt-in Slack destination (`slack` option)

## Process Flow

```mermaid
flowchart TD
    A[Receive create channel request] --> B{channelId provided?}
    B -->|No| C[Return MISSING_REQUIRED_FIELD]
    B -->|Yes| D{displayName non-blank?}
    D -->|No| C
    D -->|Yes| E{capabilities object provided with all required keys?}
    E -->|No| C
    E -->|Yes| F{NotificationChannel with channelId already exists?}
    F -->|Yes| G[Return DUPLICATE_CHANNEL_ID]
    F -->|No| H[Insert NotificationChannel row with kind derived from channelId and enabled defaulting to true]
    H --> I[Return channelId, kind, displayName, capabilities, enabled]
```

## External Dependencies

- None (operates on module-owned NotificationChannel registry only)

## Error Scenarios

- **DUPLICATE_CHANNEL_ID**: A NotificationChannel row already exists for the supplied `channelId`
- **MISSING_REQUIRED_FIELD**: One or more required input fields are missing or blank

## Test Cases

- creates a NotificationChannel row with channelId IN_APP, the supplied displayName, capabilities, and enabled=true by default
- creates a NotificationChannel row with enabled=false when explicitly supplied
- returns DUPLICATE_CHANNEL_ID when a NotificationChannel for the same channelId already exists
- returns MISSING_REQUIRED_FIELD when displayName is blank
- returns MISSING_REQUIRED_FIELD when capabilities is omitted
- accepts reserved channelIds (SMS, PUSH, SLACK, TEAMS) and persists the registry row even though no adapter is wired
- derives kind=PERSONAL for IN_APP/EMAIL/SMS/PUSH and kind=DESTINATION for SLACK/TEAMS at insert time without any caller-supplied kind input
- persists the capabilities object verbatim so template rendering can branch on supportsHtmlBody, supportsAttachments, and supportsRichActions
