# AnonymizeNotificationsForUser

## Permission Scope

auditConfig

## Overview

anonymizeNotificationsForUser is the GDPR right-to-erasure entry point. The command preserves the audit shell while purging user-identifying content: every Notification where `recipientUserId = U` has its `recipientUserId` replaced with the `TOMBSTONED` sentinel, its `payloadVars` and rendered `htmlBody` dropped, and its rendered `subject` and `body` replaced with `TOMBSTONED` (rendered content interpolates the same personal data as the input variables, so erasure must scrub both); every NotificationDeliveryAudit row where `occurredBy = U` has `occurredBy` replaced with `TOMBSTONED`. Audit row identity (`id`, `notificationId`, `eventType`, `occurredAt`, `errorClass`) is preserved so the compliance trail — "the system did notify someone at this time" — remains reconstructable. This is the deliberate "anonymize, do not delete" stance: deleting audit defeats the SOX-class obligation that audit exists to satisfy.

The command is invoked by the host application's erasure flow when a deletion request is fulfilled, or by a privileged data-protection caller — user-management does not model user deletion, so the trigger is host-app wiring. It is idempotent — re-running on an already-tombstoned user is a no-op.

## Business Rules

- `userId` is required (the subject of the erasure)
- Authorization is enforced by the command's permission gate on `notification:auditConfig` (or the command-level `notification:auditConfig:anonymizeNotificationsForUser`; the host app's erasure flow or a privileged data-protection operator); the command body assumes an authorized caller
- Replaces `recipientUserId` with `TOMBSTONED` on every Notification where `recipientUserId = userId`
- Drops `payloadVars` (sets to null / empty) on every such Notification
- Scrubs the rendered content on every such Notification: `subject` and `body` are replaced with `TOMBSTONED`, `htmlBody` is set to null
- Replaces `occurredBy` with `TOMBSTONED` on every NotificationDeliveryAudit row where `occurredBy = userId`
- Preserves audit row `id`, `notificationId`, `eventType`, `occurredAt`, and `errorClass`
- Does **not** delete any rows
- Idempotent: re-running on an already-anonymized userId is a successful no-op

## Process Flow

```mermaid
flowchart TD
    A[Receive anonymize request] --> D[Open transaction]
    D --> E[Select Notifications where recipientUserId = userId]
    E --> F[Replace recipientUserId, subject, body with TOMBSTONED; set payloadVars and htmlBody to null]
    F --> G[Select NotificationDeliveryAudit rows where occurredBy = userId]
    G --> H[Replace occurredBy with TOMBSTONED]
    H --> I[Preserve audit shell fields id, notificationId, eventType, occurredAt, errorClass]
    I --> J[Commit, return counts of anonymized notifications and audit rows]
```

## External Dependencies

- Data-protection authorization context - The caller must be authenticated as the host app's erasure flow or a privileged operator; end users cannot invoke this path

## Error Scenarios

- None — authorization is enforced by the command's permission gate, and the operation is idempotent and non-failing

## Test Cases

- replaces recipientUserId with TOMBSTONED on every Notification where recipientUserId = userId
- drops payloadVars on every such Notification
- scrubs the rendered subject, body, and htmlBody on every such Notification
- replaces occurredBy with TOMBSTONED on every NotificationDeliveryAudit row where occurredBy = userId
- preserves audit row id, notificationId, eventType, occurredAt, and errorClass
- does not delete any Notification or NotificationDeliveryAudit rows
- is idempotent on an already-anonymized userId (re-runs as a no-op)
- a subsequent BOUNCED webhook arriving for an anonymized Notification still writes a BOUNCED audit row and updates deliveryStatus
- preserves Notifications and audit rows tied to other users
