feat(runs): GET /api/runs + idempotent valuation re-runs

Merged
SW
2mo ago
+544-31

Implements the api row of recoupable/chat#1973, against the contract in recoupable/docs#305. Merge order: docs#305 → this → the chat PRs.

What this does

  • GET /api/runs — the calling account's background runs, newest first: the generic status resource behind the in-flight valuation UI. A pure read: playcount_snapshots rows mapped onto domain phases by toValuationRun (queued → queued, running → measuring, done+catalog → claimed with result.catalog_id, done unclaimed inside a 10-minute claim window → measuring, anything else → failed). The failed mapping makes the chat#1965 orphaned class (capture done, claim never landed) a terminal answer instead of an eternal spinner. kind is a required enum (valuation only today — future kinds are new values, not endpoints); limit defaults to 1, max 20. Zero new tables, zero writes.
  • Idempotent re-runs — runValuationHandler claims through resolveClaimedCatalog instead of createSnapshotCatalog directly. createMeasurementJob dedupes identical scopes onto one snapshot (60-minute reuse), and the old unconditional claim minted a duplicate catalog per re-run and repointed snapshot.catalog (observed live during #1969 verification). Re-runs now converge on the same catalog — this plus the chat-side disabled button is the whole duplicate-prevention design (no locks).

Fixes recoupable/chat#1967

Verification (local)

  • TDD red→green: toValuationRun (all six phase mappings), validateGetRunsQuery (defaults, range, unknown-kind 400), getRunsHandler (mapping, empty runs, limit passthrough, 400-before-DB, 401 passthrough), resolveClaimedCatalog (new dedicated suite pinning the #1967 reuse branch), runValuationHandler (claims via the resolver; re-run returns the existing catalog id).
  • Full suite 834 files / 4,623 tests green; tsc --noEmit and eslint clean in touched paths.

Preview verification (live queued → measuring → claimed transition across a real run, unknown-kind 400, re-run same-catalog check, docs↔live field reconciliation) to follow as a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Adds GET /api/runs for valuation runs and makes valuation re-runs idempotent to prevent duplicate catalogs. Previously re-runs minted a new catalog and finished-but-unclaimed runs could spin forever; now identical scopes reuse the same catalog and stale unclaimed runs surface as failures.

  • GET /api/runs: pure read of the caller’s runs, newest first with a stable created_at→id order. Requires kind=valuation; limit defaults to 1 (max 20). Status mapping: queued→queued, running→measuring, done+catalog→claimed, done unclaimed inside a 10‑minute window measured from updated_at→measuring, else→failed. Supports CORS preflight and sets force-dynamic. Unknown kind returns 400 with an error envelope; 401s pass through. Database read errors surface as 500 (never an empty list).
  • Idempotent re-runs: runValuationHandler claims via resolveClaimedCatalog, so identical scopes reuse the same catalog. songs_measured reports measured ISRC count (accurate on reuse). Catalog ownership falls back to the account when no organization is present.

Summary by CodeRabbit

  • New Features

    • Added an API endpoint for retrieving valuation runs with query validation, authentication, configurable result limits, and CORS support.
    • Added run status information, album counts, timestamps, and catalog results for completed runs.
    • Supported queued, measuring, claimed, and failed run states.
  • Bug Fixes

    • Improved valuation reruns by reusing claimed catalogs instead of creating duplicates.
    • Preserved accurate measured track counts when reusing catalogs.
    • Added fallback ownership handling when no organization is specified.