architectureRequest lifecycle

Request lifecycle

Every checkpoint that runs between a user’s click and your handler returning.

Detailed checkpoint reference

1. Request ID

Generated from X-Request-Id (preferred) or fresh ULID. Propagated to:

  • c.var.requestId
  • OTel trace span
  • Structured logs
  • Audit row

2. Logger context

Logger is enriched with: service, route, request_id, client_id (post-auth), brand_id (post-brand-resolve), user_id (post-introspection).

3. CORS

For browser-originated requests, validate Origin against the OAuth client’s allowed_origins list. Reject if mismatch.

4. Rate limit

Sliding window in Redis. Key: (client_id, route-class). Returns 429 + Retry-After if cap exceeded. See Rate limits and circuit breakers.

5. Token introspection

Calls services/identity/v1/oauth/introspect. Result cached in Redis for min(token_exp, 60s). Returns 401 if token invalid or expired.

6. requireScope

Checks the route’s required scopes against auth.scopes. Returns 403 + WWW-Authenticate: Bearer scope="required:scope" if insufficient.

7. Brand-scope resolution

Reads brand_id from the access token (preferred) or X-Brand-Id header (M2M). Validates against platform.brands. Attaches to context.

8. Audit row

After the handler returns, the audit middleware writes a row. If the handler didn’t c.set("audit_event_type", ...) AND the request was a mutation, the convention check audit-required would fail CI — so this should always be populated.

9. Response headers

  • X-Request-Id echoed back
  • X-RateLimit-Remaining, X-RateLimit-Reset (from rate-limit middleware)
  • CORS headers if applicable
  • Cache-Control per route

10. Structured log

Final log line: { request_id, route, method, status, duration_ms, client_id, user_id?, brand_id }. PHI never appears here — safeView() redacts anything sensitive before logging.

What can fail at each checkpoint

CheckpointFailure modeResponse
4 (rate limit)Over cap429
5 (introspection)Invalid / expired token401
5 (introspection)identity service down503 (fail-closed)
6 (scope)Missing scope403
7 (brand)Unknown brand_id400
7 (brand)brand_scope_violation403
HandlerValidation error400
HandlerNot found404
HandlerDB error500
8 (audit)Audit write failsRequest rolled back (audit + mutation in same tx)

What never happens

  • A 200 response without an audit row for a mutation.
  • A 200 response with a brand_id mismatch between token and rows.
  • A 200 response containing PHI fields if the token’s scopes don’t include them.
  • A response that omits X-Request-Id — required for cross-service correlation.