# ListAuditBySource

## Overview

ListAuditBySource returns audit rows for the notifications a single source event fanned out **to the calling user**, joined to the parent Notification's polymorphic `(sourceType, sourceId)`. A user uses this to answer "what was I notified of for this record?".

## Business Rules

- Accepts `{ sourceType, sourceId }`
- Self-scope: a caller sees only the audit rows of notifications addressed to them (`recipientUserId == caller.actorId`)
- A caller whose fan-out includes no notification addressed to them receives an empty page (not an error)
- Returns audit rows for every caller-owned Notification whose parent carries the supplied `sourceType + sourceId`
- Returns an empty list when no such Notifications exist for the supplied source
- Rows are ordered by `occurredAt` ascending (chronological)
- Each row carries the standard NotificationDeliveryAudit fields (`id`, `notificationId`, `eventType`, `occurredAt`, `occurredBy`, `errorClass`, `errorDetail`)
- Anonymized rows (post-retention-sweep or post-erasure) are returned with structural fields preserved and `errorDetail` / `occurredBy` cleared

## Process Flow

```mermaid
flowchart TD
    A[Receive sourceType, sourceId] --> B[Load Notifications where sourceType, sourceId match and recipientUserId == caller]
    B --> C[Load NotificationDeliveryAudit rows joined by notificationId]
    C --> D[Order by occurredAt asc]
    D --> E[Return list]
```

## External Dependencies

- None

## Error Scenarios

- **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 audit rows for every Notification fanned out from the supplied source
- returns rows ordered by `occurredAt` ascending
- returns an empty list when the source has no Notifications
- restricts the caller to audit rows of their own notifications
- returns an empty list when the fan-out includes no notification addressed to the caller
- returns anonymized rows with `errorDetail` and user-driven `occurredBy` cleared while structural fields are preserved
- honors limit/offset pagination and reports hasNextPage when more rows remain
