# CreateNotificationSubscription

## Permission Scope

subscriptionSelf

## Overview

createNotificationSubscription is the idempotent self-service `subscribe` entry point for follower-style relationships between a user and an arbitrary domain record `(sourceType, sourceId)`. The command inserts a `NotificationSubscription { userId, sourceType, sourceId, subscribedAt }` row scoped to one row per `(userId, sourceType, sourceId)` triple. If a row already exists the command returns it unchanged — `subscribedAt` is not refreshed and no duplicate is inserted. The caller must be the target user; emitter-side recipient hints belong in `logNotificationEvent.payload.recipients`, not in this persistent subscription surface.

## Business Rules

- `userId`, `sourceType`, `sourceId` are required
- The caller must be the target user (`ctx.actorId == userId`)
- The triple `(userId, sourceType, sourceId)` is unique — re-subscribing returns the existing row
- The command does not validate that the source record exists; subscriptions to unknown / future / deleted sources are allowed (dangling rows are harmless)

## Process Flow

```mermaid
flowchart TD
    A[Receive subscribe request] --> B{Caller is target user?}
    B -->|No| C[Return FORBIDDEN]
    B -->|Yes| E{Existing NotificationSubscription for userId, sourceType, sourceId?}
    E -->|Yes| F[Return existing row, idempotent no-op]
    E -->|No| H[Insert NotificationSubscription with subscribedAt = now]
    H --> I[Return new row]
```

## External Dependencies

- None (operates on module-owned NotificationSubscription only)

## Error Scenarios

- **FORBIDDEN**: Caller is not authorized to perform this operation in the target scope

## Test Cases

- inserts a NotificationSubscription row with subscribedAt set to current timestamp on first call
- returns the existing row without inserting a duplicate when called a second time with the same triple (idempotent)
- subscribedAt is not refreshed on idempotent re-call
- creates two distinct rows when two different users subscribe to the same source
- creates two distinct rows when the same user subscribes to two different sourceIds under the same sourceType
- creates two distinct rows when the same user subscribes to the same sourceId under two different sourceTypes
- returns FORBIDDEN when the caller is not the target user
- accepts a sourceId that does not correspond to any existing record (no source-existence validation)
- concurrent re-subscribe calls produce exactly one row (uniqueness enforced at storage layer)
