Phase 1 of AUDIT-2026-Q3.md item P1.10.
Codebase had 2,532 except Exception: blocks and zero AppError class.
Bare exceptions made debugging in production impossible.
This change adds a strict typed hierarchy on top of the existing
register_error_handlers() (which app/lifespan.py imports):
AppError (base, status 500, code internal_error)
AuthError 401 auth.unauthorized
RateLimitError 429 ratelimit.exceeded
ValidationError 400 validation.failed (RMI-level, not pydantic)
NotFoundError 404 not_found
UpstreamError 502 upstream.failed
Each carries status_code (HTTP), code (machine-readable), and a
context dict that handlers forward to Sentry without code changes.
register_error_handlers() also now installs an AppError handler that
returns exc.to_dict() with a request_id, in addition to the existing
StarletteHTTPException / ValueError / Exception handlers.
Refs AUDIT-2026-Q3.md P1.10.