# Refactor: sanitize 5xx responses via global IExceptionHandler

**Task**: AZ-353_refactor_sanitize_5xx_responses
**Name**: Centralized exception handler with sanitized ProblemDetails
**Description**: Replace per-endpoint `try/catch (Exception)` + `Results.Problem(detail: ex.Message)` with a global `IExceptionHandler` that returns sanitized ProblemDetails (correlation ID + generic title) and logs the full exception server-side.
**Complexity**: 3 points
**Dependencies**: None
**Component**: Api
**Tracker**: AZ-353
**Epic**: AZ-350

## Problem

`SatelliteProvider.Api/Program.cs` has six endpoint handlers (lines 139-143, 170-174, 206-210, 226-230, 245-249, 265-269) that each catch `Exception` and return `Results.Problem(detail: ex.Message, statusCode: 500)`. The `detail` ships the exception message — including stack-trace fragments, file paths, SQL error text, and Google API error bodies — back to the client.

## Outcome

- 5xx responses no longer leak internal exception messages.
- Server-side logs contain the full exception + correlation ID for each 500.
- Per-endpoint try/catch boilerplate is removed.
- 37 unit + 5 smoke tests stay green (with assertions on `ProblemDetails.detail` updated).

## Scope

### Included
- Add an `IExceptionHandler` (or `UseExceptionHandler` middleware) in `Program.cs`.
- Generate a correlation ID per request, include it in both the response body and the server log entry.
- Remove the per-endpoint catches that only re-emit `ex.Message`.
- Update tests that assert on `ProblemDetails.detail` to assert on the sanitized shape.
- Specific 400 paths (e.g., `ArgumentException` in `CreateRoute`) keep their typed handling.

### Excluded
- Changing HTTP status codes for 4xx paths.
- Adding new structured error categories (deferred).
- Changing the logger sink configuration.

## Acceptance Criteria

**AC-1: 5xx body is sanitized**
Given any endpoint that throws an unhandled exception
When the client receives the 500 response
Then `ProblemDetails.detail` does not contain the original exception message; the body has a generic title + correlation ID.

**AC-2: Server log has the full exception**
Given the same scenario as AC-1
When the application logs the failure
Then the log entry contains the exception type, message, stack trace, and the same correlation ID returned to the client.

**AC-3: 4xx paths preserved**
Given a request that triggers `ArgumentException` in `CreateRoute`
When the endpoint runs
Then the response is HTTP 400 with the existing typed shape (not 500).

**AC-4: Tests stay green**
Given the post-refactor build
When `scripts/run-tests.sh --smoke` runs
Then all 37 unit + 5 smoke scenarios pass (with `ProblemDetails.detail` assertions updated).

## Constraints

- HTTP 500 status code preserved for unhandled exceptions.
- No new dependencies beyond ASP.NET Core 8 built-ins.

## Risks & Mitigation

**Risk 1: tests that assert on `detail` text break**
- *Risk*: existing unit/integration tests may inspect `ProblemDetails.detail`.
- *Mitigation*: update them to assert on the new sanitized shape (title + correlationId) in the same PR.

**Risk 2: clients depend on the leaky message**
- *Risk*: the API has been live; some integrator may parse the message.
- *Mitigation*: this is a security improvement; document the change in the OpenAPI spec.

Full change entry: `_docs/04_refactoring/03-code-quality-refactoring/list-of-changes.md` (C03).
