Phase 2: scope cross-program alerts by household_assignments (full #590 fix)

Context

Follow-up filed alongside the #590 (closed) identity-gate fix. #590 (closed)'s MR closes the worst-of-both-worlds 'decorative worker_id param' state — a caseworker can no longer pass another caseworker's ID and get back the unscoped alert list. That fix is identity-only: it proves the caller is who they say they are, but it does NOT yet filter the result set down to the caseworker's actual assigned cases.

What this issue covers

The audit's preferred resolution (#590 (closed) Option 1) requires:

  • Joining canopy-applications.household_assignments against program_determinations to filter the returned alert rows to just the caseworker's owned households.
  • Cross-service call from canopy-eligibility into canopy-applications (or a denormalized read-model of household_assignments) — neither exists today.

Acceptance criteria

  • Caseworker tokens calling GET /v1/eligibility/cross-program-alerts?worker_id=<self> return ONLY alerts for households assigned to that caseworker.
  • Supervisor / admin tokens continue to receive the unscoped jurisdiction view (this is intentional triage tooling — see #590 (closed) doc).
  • Service callers continue to receive the unscoped view.
  • Existing #590 (closed) unit + integration tests stay green.
  • New integration test: seed one assignment for caseworker A, one for caseworker B, assert each sees only their own.

Out of scope

  • The supervisor 'scope down to one worker's view for triage' affordance is already enabled by #590 (closed) (supervisor passes any worker_id and the identity gate does not fire); narrowing the result set when supervisor passes worker_id belongs to this issue too.
  • household_assignments table design + ownership lives with canopy-applications; this issue depends on whatever shape lands there.

Labels

service::eligibility, type::feature, priority::medium, workflow::needs-spec, compliance::pub-1075, program::cross-program

Refs #590 (closed) #523 (closed)