feat(runs): GET /api/runs + idempotent valuation re-runs
Merged
SW
2mo ago+544+544+012345678901234567890123456789-31-31-01234567890123456789
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_snapshotsrows mapped onto domain phases bytoValuationRun(queued→ queued,running→ measuring,done+catalog → claimed withresult.catalog_id,doneunclaimed 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.kindis a required enum (valuationonly today — future kinds are new values, not endpoints);limitdefaults to 1, max 20. Zero new tables, zero writes.- Idempotent re-runs —
runValuationHandlerclaims throughresolveClaimedCataloginstead ofcreateSnapshotCatalogdirectly.createMeasurementJobdedupes identical scopes onto one snapshot (60-minute reuse), and the old unconditional claim minted a duplicate catalog per re-run and repointedsnapshot.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 --noEmitand 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. Requireskind=valuation;limitdefaults 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 setsforce-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:
runValuationHandlerclaims viaresolveClaimedCatalog, so identical scopes reuse the same catalog.songs_measuredreports 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.