fix: replace create_eligibility_request unconditional sweep with idempotency policy

Audit surfaced (external review of MR-e demo branch, 2026-05-26 EOD).

services/canopy-eligibility/src/store/mod.rs:17 currently marks every pending / in_progress row for a given (application_id, household_id) pair as failed before inserting the new row. The comment acknowledges this as a workaround for the partial unique index idx_unique_pending_request introduced in add_constraints.sql:8.

UPDATE eligibility_requests SET status = 'failed', ...
WHERE application_id = $1 AND household_id = $2
  AND status IN ('pending', 'in_progress');

Why this is a bug under real concurrency:

  • A legitimate in-flight orchestrator run gets marked failed by a second Run Determination click
  • Quietly drops the orchestrator's eventual INSERT of the actual determination (no error surfaces)
  • The intended SQL invariant (one active request per pair) is bypassed via the same code path that was supposed to enforce it

Why it's currently in: demo retry path. Without the sweep, re-clicking Run Determination after a transient orchestrator error 500s on duplicate key value violates unique constraint "idx_unique_pending_request". The sweep papers over that for the single-worker demo.

Correct behavior:

  1. INSERT first; if it fails with the unique-constraint violation, return 409 Conflict with a retry_after header tied to the in-flight row's created_at + TTL.
  2. Stale-TTL sweep runs in a background job (e.g. 10-min cutoff for pending, 60-min for in_progress) rather than inline on every create.
  3. Worker UI surfaces the 409 as "A determination is already running for this case (started X minutes ago)" — never silently overwrite.

Scope: services/canopy-eligibility/src/store/mod.rs::create_eligibility_request + a new background sweep job + canopy-web /cases/{hid}/run-determination 409 handling.

Labels: type::bug, priority::high, service::eligibility, program::cross-program