Type the observed release format in release cases

SI
9d ago
+178-1

Summary

The database now projects release_format on every release case from the already-saved Spotify album observation (recoupable/database#109). This PR gives that field a typed API contract and documents it, without changing behavior: ReleaseFormatObservation (new lib/context/releaseFormatTypes.ts) is attached to ReleaseCaseProjection, and the Context README describes its explicit states. format_state is single/album/compilation only when Spotify reports exactly that type, otherwise unknown; track_coverage is full, partial or uncollected; disc_count is a definite number only with full track coverage and a disc number on every slot (otherwise null), and multi_disc is true once any slot shows disc 2 or higher, false only for a definite single disc, otherwise null; upc is present only when the provider reported one (upc_state is observed, not_observed or uncollected); reissue and physical_format are typed as the literal "unknown" because Spotify metadata cannot establish them. This advances ticket Done-when 1 (distinguish singles, albums and formats on canonical releases with ordered, disc-aware membership).

The API performs no reinterpretation and no provider calls: read_release_case still returns the RPC body as is. The shared dispatcher (processContextOperation.ts) and MCP operations are untouched.

Refs recoupable/app#2139 (child of epic recoupable/app#2116).

Merge sequencing

docs (no change) → database recoupable/database#109 → api (this PR) → app recoupable/app#2220. Merge database#109 first: before that migration is released, the hosted read returns no release_format, which the app tolerates (optional field) but this type would describe incorrectly. Depends on no other OPEN PR; builds on merged api#992 / database#96 and the 20261008 release-case migrations.

Verification

  • Ran pnpm exec vitest run lib/context/__tests__/releaseFormatProjection.test.ts lib/context/__tests__/releaseCaseOperations.test.ts lib/context/__tests__/releaseOperation.test.ts → 3 files, 16 passed, 0 failed.
  • Ran pnpm exec vitest run lib/context (full Context suite) → 68 files passed, 8 skipped; 330 tests passed, 8 skipped (live/paid tests), 0 failed.
  • Ran pnpm exec tsc --noEmit → 199 errors, byte-identical to the origin/main baseline (199, all in unrelated files such as lib/agent/contextManagement/__tests__ and lib/chats/__tests__); 0 errors in changed files.
  • Ran pnpm exec eslint on the 3 changed TypeScript files → clean; pnpm exec prettier --check on those files plus lib/context/README.md → clean.
  • git diff origin/main -- lib/context/processContextOperation.ts lib/mcp → empty.
  • Red → green: releaseFormatProjection.test.ts was written first; tsc --noEmit failed with 4 new errors (Cannot find module '../releaseFormatTypes', 'release_format' does not exist in type 'ReleaseCaseProjection' ×3). After adding the type the 4 errors disappeared and all 3 tests pass. The tests assert the RPC body is returned by reference (no API-side fill-in), that read_release_case still rejects client-supplied release_format/format_state/upc, and that reissue/physical_format are typed as only "unknown".
  • Red → green (review follow-up, second commit): saved review snapshots are immutable, so reviews saved before the migration lack release_format. A new compile-time test assigns such a legacy snapshot to ReleaseCaseReview["snapshot"]; tsc failed with TS2741: Property 'release_format' is missing. The snapshot type now makes release_format optional while the live projection keeps it required; tsc is back to the 199-error baseline.
  • Third commit (independent review follow-up, matching database#109's partial-coverage fix): track_coverage was added to the test fixtures first; tsc --noEmit failed with 6 new errors (TS2353 'track_coverage' does not exist in type 'ReleaseFormatObservation' ×3, TS2339 ×3). After adding the field, tsc is back to 199 errors with none in changed files. Rerun after the last change: pnpm exec vitest run lib/context → 68 files passed, 8 skipped; 330 tests passed, 8 skipped, 0 failed. eslint and prettier --check on the changed files → clean.
  • CI caveat: these tests guard a type-only contract. Under plain vitest they pass even without the type (type imports are erased and expectTypeOf is inert without --typecheck); only tsc --noEmit shows the red, and api CI runs no tsc gate that covers lib/context. CI therefore would not catch a regression of this type.
  • Not verified: hosted deployment (Vercel built its automatic PR preview; not exercised), the production database (database#109 is not applied there), live Spotify payloads, any live provider, the MCP/agent surface end to end.

Out of scope / known gaps

  • After database#109 is released, every previously saved release review reads stale: true because the projected body (and so the fingerprint) changed, not the evidence; operators must re-review those cases. See database#109 for the approval note.
  • Catalog member expansion with bounded concurrency and resumable child requests (Done-when 2 remainder): needs a Vercel Workflow plus child-request RPCs and the shared dispatcher; member collection stays policy-blocked.
  • Reissue and physical-format detection: not observable from Spotify; needs other evidence sources.
  • OpenAPI documentation of release-case actions in recoupable/docs (none exists today; left to the shared API/MCP exposure ticket #2125 to avoid a JSON merge conflict).
  • label is an observed string; it does not verify rights, ownership or distribution.

🤖 Generated with Claude Code