# CLAUDE.md

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

## Overview

`ows-product-digital` is a Flask microservice that manages digital audio products (releases) at The Orchard. It handles product creation, updates, submission/approval workflows, pricing, metadata validation, and integration with downstream systems.

## Commands

### Setup
```bash
poetry install          # Install all dependencies (including dev)
poetry install --only main  # Production deps only
cp .env.shadow .env     # Set up local environment credentials
```

### Run
```bash
poetry run python dev.py        # Start Flask dev server (default port 5000)
PORT=5001 poetry run python dev.py  # Custom port
make up_dev                     # Run in Docker
```

### Test
```bash
# All unit tests with coverage
poetry run py.test tests/ --ignore=tests/integration --cov product_digital --cov-report term-missing

# Single test file
poetry run py.test tests/functional/test_put_product.py

# Single test by name
poetry run py.test tests/functional/test_put_product.py::test_put_product_with_valid_product_payload_no_highlights_included

# Integration tests (requires `awsume prod` first)
poetry run py.test tests/integration --junitxml=pyunit.xml
make docker_test_integration
```

### Lint
```bash
poetry run ruff check product_digital/ tests/ application.py dev.py
```

### Docker (mirrors CI)
```bash
make docker_unit_lint       # Run unit tests + lint in Docker
make docker_test_integration  # Run integration tests in Docker
```

## Architecture

### Layer Structure

The service follows a strict three-layer architecture:

1. **Handlers** (`product_digital/handlers.py`, `product_digital/unsafe_handlers.py`) — Flask route definitions. Extract request data, call logic functions, return `flaskify(response)`. Use `@json_schema.validate_request_body` / `@json_schema.validate_request_headers` decorators for input validation.

2. **Logic** (`product_digital/logic/`) — Business logic layer. Functions here orchestrate models and return `oto.response.Response` objects. Key modules: `product.py` (create/update/submit), `product_workflow.py`, `validation.py`, `meta_language.py`.

3. **Models** (`product_digital/models/`) — Database access and external service calls. MySQL models use SQLAlchemy via the `mysql.db_session()` context manager. Files like `ows_product.py`, `ows_track.py` are wrappers around calls to other OWS microservices via `owsrequest`.

### Key Infrastructure

- **Database**: MySQL in QA/prod via SQLAlchemy; SQLite in-memory for unit tests (set by `config.ENVIRONMENT == 'test'`). Use `mysql.db_session()` context manager for all DB access. `mysql.db_session_wrap` decorator for functions that optionally accept a session.
- **Neo4j**: Connected via `connector_neo4j` for vendor graph data.
- **External services**: Calls to other OWS services (`ows-product`, `ows-track`, etc.) are made via `owsrequest.request` helpers. Service names are defined in `product_digital/constants/services.py`.
- **Auth/Access**: Request authentication handled by `owsrequest.flask_request` with rules in `product_digital/access_rules.yml`. Endpoint-level identity checks use the `@only_for_identity(identity_uuid)` decorator from `product_digital/auth.py`.
- **Authorization (PP)**: Permissions Platform authorization via `python_pdp_sdk`. The `PdpAuthorizationBackend` is already wired (`api.py:71`), but only `/meta-language` calls `is_authorized` today — and that call is observability-only (result discarded, errors logged not enforced). See `AUTH.md` for the full per-endpoint auth posture and PP migration plan.
- **Feature flags**: Managed via `pythonfeatures` (Split). Accessed through `product_digital/features.py`.
- **API spec**: RAML spec in `spec/api/`. JSON schemas in `spec/schema/` loaded at startup in `config.py`.

### Unsafe Handlers

`unsafe_handlers.py` contains endpoints that perform destructive or privileged operations (hard delete, display UPC update). These are gated by `@only_for_identity(config.SOME_IDENTITY)` and must always remain restricted. UNSAFE-prefixed functions should never be called from regular logic paths.

### Auth Posture & PP Migration (`AUTH.md`)

`AUTH.md` (repo root) is a generated scan of every endpoint's current authorization posture and a phased plan for migrating to Permissions Platform (PP) enforcement. Key takeaways:

- **Layered, legacy-first auth.** Most endpoints rely on `access_rules.yml` (middleware) **plus** a handler-level `verify_grass_headers` call. `verify_grass_headers` is header-only and **permissive by default** (no `required=True` anywhere); ~16 endpoints add `verify_grass_ownership` for a vendor/subaccount ownership check.
- **Access rules are log-only in prod.** `access_log_only = config.ONLY_LOG_ACCESS_ERRORS` is `True` in prod (does not block) and `False` in qa/dev. So in production the effective gate is the permissive grass check — and 3 endpoints (`copy`, `simple_submit`, `DELETE /product/<id>`) have no handler-level auth at all.
- **PP is not yet enforced anywhere.** Backend is wired; per-handler `is_authorized` calls still need to be added (Phase 2 shadow → Phase 3 enforce, Template C for the permissive endpoints).
- **Needs human review:** the three `@only_for_identity` UNSAFE handlers and `submit_product`'s `is_identity_authorized` allowlist — identity checks that bypass PP's resource/action flow.

Consult `AUTH.md` before adding or changing endpoint authorization, and update it when auth behavior changes.

### Test Patterns

- **Unit/functional tests** use SQLite in-memory via `ENVIRONMENT=test`. The `@db.test_schema` decorator (from `tests/testutils/db.py`) creates and tears down the full schema around a test.
- **Factories** in `tests/factories/` (e.g., `release.py`, `release_artist.py`) use factory patterns to build model instances. Call `.build()` for in-memory instances, then `db.seed_models([...])` to persist.
- **Mocking**: External service calls (`owsrequest.request`, model-level functions) are mocked with `mocker.patch.object` or `flexmock`. The `conftest.py` provides shared fixtures including `client`, `valid_headers`, `valid_product_payload`, `empty_success`.
- **Integration tests** in `tests/integration/` hit a real QA environment and require AWS credentials (`awsume prod`).

### Configuration

`product_digital/config.py` loads from `.env` (via `python-dotenv`) and AWS Secrets Manager (`secrets_manager`). The `ENVIRONMENT` env var (`dev`/`qa`/`prod`/`test`) controls DB connection, logging, and feature behavior.
