# SearchNotificationDeliveryAudit

## Overview

SearchNotificationDeliveryAudit is the self-service search surface over NotificationDeliveryAudit. Filters can be combined freely: by `eventType` (e.g., every row that reached `DELIVERED`), `dateRange`, `recipientUserId`, and the parent Notification's `(sourceType, sourceId)`. A user runs the query for self-service ("show me everything I've been notified of").

## Business Rules

- Accepts `{ eventType?, dateRange?, recipientUserId?, sourceType?, sourceId? }`; all filters are optional and combined with AND logic. A convenience top-level `from` / `to` pair may be supplied as an alias for `dateRange.{from, to}`; when both are present `dateRange` takes precedence. Standard pagination inputs (`limit`, `offset`) are also accepted
- Self-scope: results are always restricted to the caller's own audit rows (`recipientUserId == caller.actorId`); an explicit `recipientUserId` filter must equal the caller's `actorId`, otherwise `FORBIDDEN`
- Returns an empty list when no rows match
- Rows are ordered by `occurredAt` descending so the most recent activity appears first
- The query returns stored rows verbatim — it performs no redaction itself. Anonymization is applied **at rest** by `anonymizeNotificationsForUser` / the retention sweep (which clear freeform `errorDetail` and user-driven `occurredBy` in place), so an already-anonymized row comes back with those fields already cleared while structural fields are preserved; a row not yet swept comes back with its freeform fields intact

## Process Flow

```mermaid
flowchart TD
    A[Receive optional filters] --> B{recipientUserId supplied AND != caller?}
    B -->|Yes| C[Error: FORBIDDEN]
    B -->|No| R[Resolve caller-owned notificationIds via parent, applying sourceType + sourceId filters]
    R --> D[Build NotificationDeliveryAudit query scoped to those notificationIds]
    D --> E[Apply eventType filter if provided]
    E --> F[Apply dateRange filter if provided]
    F --> I[Order by occurredAt desc]
    I --> J[Return list]
```

## External Dependencies

- None

## Error Scenarios

- **FORBIDDEN**: Caller is not authorized to perform this operation in the target scope
- **NO_MATCH**: No matching row exists for the supplied input; null or empty result is returned

> Returning `NO_MATCH` is a normal empty/null result rather than a thrown error — the query returns the null/empty value and does not raise `NoMatchError`.

## Test Cases

- returns rows matching the supplied `eventType` filter
- returns rows whose `occurredAt` falls within the supplied `dateRange`
- returns rows for the supplied `recipientUserId` when it matches the caller
- returns rows whose parent Notification matches the supplied `sourceType + sourceId`
- combines multiple filters with AND logic
- returns rows ordered by `occurredAt` descending
- rejects a caller searching for another user's rows with FORBIDDEN
- scopes searches without `recipientUserId` to the caller's own rows
- returns an empty list when no rows match the filters
- returns anonymized rows with structural fields preserved and freeform fields cleared
- returns a non-anonymized row's freeform fields intact (the query never redacts at read time)
