fix: utoipa IntoParams generates 'in: path' for query parameters (3+ endpoints)

External audit finding #7 (closed) (2026-05-26), confirmed by reading generated OpenAPI snapshots.

Endpoints that take Query<...> parameters are emitting "in": "path" in the generated OpenAPI spec because utoipa::IntoParams defaults to parameter_in = Path when no explicit annotation is provided.

Confirmed-affected endpoints (from docs/modules/ROOT/openapi/eligibility.json + appeals.json):

  • GET /v1/eligibility/case-status?household_id=...eligibility.json:459
  • GET /v1/eligibility/cross-program-alerts?worker_id=...eligibility.json:512
  • GET /v1/eligibility/determinations?household_id=... (new in MR-e !380 (merged)) — eligibility.json:513 (per #586 / #587)
  • GET /v1/appeals/hearings/upcoming?days=...appeals.json:796

Likely-affected (utoipa::IntoParams pattern repeats):

  • Any Query<SomeQuery> handler where SomeQuery derives IntoParams without #[into_params(parameter_in = Query)] or per-field #[param(in = Query)].

Impact:

  • Generated typed clients (e.g. via openapi-generator) emit positional path-segment params instead of query string params → 404 at runtime
  • Swagger UI documents the endpoints incorrectly — readers can't tell whether to pass ?household_id=X or /X

Fix:

  1. Add #[into_params(parameter_in = Query)] above each affected struct (single line per struct).
  2. Sweep the workspace: grep -rn "Query<.*>\|IntoParams" services/*/src/api/ and confirm every struct used in a Query<> extractor has the annotation.
  3. Add a cargo xtask api-docs --check step that asserts no "in": "path" schemas on endpoints whose handler signature uses Query<>.

Severity: medium — not a runtime bug (axum routes ignore the spec annotation) but generated clients are wrong and #587 paging follow-up will trip over the same drift if not fixed first.

Labels: type::bug, priority::medium, service::ci, program::infrastructure

Refs #586 #587