[Story-1131] Enforce ADR-102 lazy app API startup

Story 1131Epic None11 files · +417/−50

PR #287 · pramaan-io/pramaan-functions · merged 2026-06-09

Why this matters

Almost five seconds of cold start — and every millisecond is paid by the first user.

Why this PR exists

The cost didn't disappear. It just moved from Init into the first billed request.

The fix — Eager the structure, lazy the state — Init only does what every cold start actually needs.

How it flows

sequenceDiagram
  autonumber
  participant init as Lambda Init
  participant warm as Warm lifetime
  participant req as First request
  init->>init: App + routers build (eager)
  init-->>warm: Caches sit empty (cold)
  req->>req: Middleware installs tracing on first pass
  req->>warm: First DB call → engine + dotenv load
  req->>warm: First verify → JWKS client fetches once
  req->>warm: First AWS call → boto3 client cached

router_ws.py · +34 — new methods

Methodin → outwhat it does
_sqs_client(region_name) → Any
_aws_client_error_types() → tuple[type[Exception], ...]
_cached_management_client(region_name, endpoint_url) → Any

What changed, file by file

WebSocket router; defers SQS and API Gateway Management client construction to first use, removing boto3 from App API handler import.

new methodsignature
_sqs_client(region_name) → Any
_aws_client_error_types() → tuple[type[Exception], ...]
_cached_management_client(region_name, endpoint_url) → Any

CloudWatch Logs emitter; builds the Logs client lazily on first emit and caches it, removing boto3 construction from import time.

new methodsignature
_logs_client(region_name) → Any
_client_error_type() → type[Exception]

App factory middleware; moves service and repository tracing installation to the first request pass via a dedicated middleware hook.

new methodsignature
_install_service_repository_tracing_on_first_request(request, call_next)
ensure_service_repository_tracing_installed() → None

Document service; wraps S3 client creation in a lazy cached accessor, so document path no longer builds AWS clients at import.

new methodsignature
_document_s3_client(region_name) → Any

Shared S3 offload helper; introduces cached S3 client accessor keyed by region to keep boto3 out of the import path.

new methodsignature
_cached_s3_client(region_name) → Any
_s3_client(settings) → Any

Projection outbox publisher; lazy SQS client accessor defers AWS client creation to the first publish, not to module import.

new methodsignature
_sqs_client(region_name) → Any

Clerk auth helper; caches PyJWKClient per JWKS URL so import no longer fetches or loads verification material.

new methodsignature
_jwks_client(jwks_url) → jwt.PyJWKClient

Async DB session module; keeps the async engine lazy and defers local dotenv loading to the first call, not import time.

new methodsignature
_load_local_db_env() → None

Health route; defers the legacy sync DB helper import until the /health endpoint actually runs, keeping handler import light.

new methodsignature
check_connection() → bool

Files changed (11)

File±
docs/walkthroughs/story-1131-walkthrough.html+164
src/app_api/router_ws.py ★+34 −15
src/domains/document/service.py+15 −8
src/shared/app_factory.py ★+17 −1
src/shared/auth/clerk.py+8 −1
src/shared/aws/cloudwatch_logs.py ★+25 −12
src/shared/db/session.py+8 −5
src/shared/projection_outbox.py+9 −2
src/shared/routes/health.py+7 −1
src/shared/s3_offload.py+11 −5
tests/unit/test_lambda_startup_contract.py+119