# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Purpose

`sr-fanout` is triggered when a **Vendor** or **SubAccount** entity changes, and it propagates ("fans out") that change to all associated OrchardSoundRecordings (OSRs) by calling the OWS `PATCH /sound_recordings/touch` endpoint in batches. The goal is to force those OSRs to recompile with updated data.

## Development Commands

Run from this directory (`lambda/sr-fanout/`):

```bash
# Run tests and linting
docker compose up --build lint-and-test

# Run with exit codes (CI-style)
docker compose up --exit-code-from lint-and-test --abort-on-container-exit --build lint-and-test

# Skip linting
SKIP_LINT=true docker compose up --build lint-and-test

# Run a specific test
TEST_ARGS="-k test_handler" docker compose up --build lint-and-test

# Start the function locally (port 9000)
docker compose up --build -d function

# Invoke locally
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d @tests/sample_event.json

# Restart after code changes (no rebuild needed for .py files)
docker compose restart function
```

## Architecture

### Handler Flow (`src/app.py`)

1. Receives an event with `label` (must be `"Vendor"` or `"SubAccount"`), `id` (int), and `timestamp` (UTC ISO 8601).
2. Validates that `label` is one of the two accepted types and that `timestamp` has UTC timezone info.
3. Loops calling `ows_sound_recordings.touch_sound_recordings()` in batches of `BATCH_SIZE` (10,000) until the API returns fewer records than the batch size, indicating all matching OSRs have been touched.
4. Returns `{'total_updates': <total>}`.

### Connector (`src/connectors/ows_sound_recordings.py`)

Thin wrapper around `src.common.connectors.ows_sound_recordings.call`. Issues a `PATCH /sound_recordings/touch` with `resource_id`, `resource_type` (lowercased), `modified_before`, and `limit`. Retries on 408, 502, 503, 504. Returns `nodes_updated` from the response JSON.

### Key Config (`config.py`)

| Variable | Default | Description |
|----------|---------|-------------|
| `BATCH_SIZE` | `10000` | Records per touch API call |
| `ENVIRONMENT` | `dev` | Controls secrets resolution and OWS environment targeting |
| `APPLICATION_NAME` | `lambda-sr-fanout` | Passed as the caller identity to OWS |

`SENTRY_DSN` is fetched at module load time via `LambdaSecretsManager`; Sentry is only initialized when this value is non-empty.

## Testing Notes

- Unit tests mock `src.connectors.ows_sound_recordings.touch_sound_recordings` directly; the batching loop is tested by making the mock return `10000` on the first call and a smaller number on the second.
- Integration tests target `qa-lambda-sr-fanout` via boto3 and are intentionally skipped in CI (the placeholder test asserts `1 == 0` — replace it before running real integration tests).
- The `sample_event.json` is an empty `{}` and is not used by current unit tests; supply a real event when invoking locally.
