Accept unreleased recordings and planned releases without IDs
Summary
Adds ingest_unreleased_recording and ingest_planned_release to the shared Context operation, so they are available over both HTTP POST /api/context and the delegated MCP tools import_unreleased_recording_context / import_planned_release_context. An account can now save an unreleased song or a planned album before it has an ISRC, UPC or store listing.
- Validation: strict Zod schemas in
unreleasedMusicOperationSchemas.tsrejectisrc,upc,store_idsandspotify_idkeys before authorization. They also mirror the database checks, so input the database would reject is a 400 rather than a 409 that tells the client to retry: unknown lifecycle states, control characters in titles and labels, non-lowercase or whitespace-containing http(s) links, more than 20 links, and a recording repeated in one tracklist. An omitted lifecycle is stored asunknownfor both entries. The MCP descriptions tell agents identifiers are rejected and that a planned release may carry a tracklist of saved recording subject IDs. - Storage:
processUnreleasedMusicOperation.tscalls the new service-role RPCs after the usual actor/workspace authorization. The database assigns an internal identity, and ISRC, UPC and store IDs stayunknown(Done-when 1 and 2). - No side effects: nothing is dispatched, looked up or charged.
- Multiple releases: one recording subject can be referenced by several planned releases (Done-when 3, partial).
- Company assessment (user-visible change): the
company_onboardingbrief recipe now includes theunreleased_recordingandplanned_releasetopics. Missing topics and gaps are computed against every recipe topic, so every company assessment, including Release-review assessments of released Spotify releases, now shows two more "What's missing" entries (unreleased recording,planned release) and two moreunavailablegaps when none were submitted. This follows the existing pattern of listing inapplicable topics; chat renders topic strings generically, so nothing breaks. When planned music is submitted, the assessment lists it with its explicit identifier gaps, andrecording_metadata/release_metadatastay missing for it.
Refs recoupable/app#2141 (child of epic recoupable/app#2116).
Merge sequencing
docs (recoupable/docs#370) → database (recoupable/database#111) → api (this PR). Deploy only after database#111 is applied; before that, the RPCs do not exist and the actions return 409. This PR depends on no other open PR. api#981 and the related open PRs edit the same shared files (processContextOperation.ts, contextToolOperations.ts, callContextRpc.ts, fullOAuthCatalog.test.ts, lib/context/README.md). The edits here are small and appended at the end of each list, and the MCP tool count goes from 84 to 86. Expect a trivial rebase if those merge first.
Verification
- Ran at head 8f1a6b64 (0 commits behind main):
pnpm exec vitest run lib/context→ 68 files passed, 8 skipped; 335 tests passed, 8 skipped, 0 failed.pnpm exec vitest run lib/context lib/mcp/oauth/__tests__/{fullOAuthCatalog,registerFullOAuthTools,contextToolOperations}.test.ts→ 71 files passed, 8 skipped; 347 tests passed, 8 skipped, 0 failed.pnpm exec vitest run lib/mcp/oauth lib/supabase/context_requests→ 87 tests passed. One file,fullOAuthRouting.test.ts, fails to load becauselib/mcp/extension/generated-ui.tsis a gitignored build artifact missing from this worktree (environment only).pnpm exec tsc --noEmit→ 199 errors, none in any changed file. An independent review compared against a main-equivalent copy and found them pre-existing.pnpm exec eslintandpnpm exec prettier --checkon all changed files → clean.
- Red → green:
- Before the implementation,
unreleasedMusicOperation.test.tsfailed 7 of 7 (unknown action, or pathactioninstead ofrecording), the newcompanyAssessment.test.tscase failed withexpected [] to deeply equal [...], andfullOAuthCatalog.test.tsfailed with "length of 86 but got 84". The rejection tests include positive controls, so they cannot pass vacuously. - Review follow-up: with the previous
unreleasedMusicOperationSchemas.ts, 3 of the 8 tests fail (control characters accepted, omitted release lifecycle rejected, and uppercase/whitespace links plus duplicate tracklist IDs accepted); all 8 pass with the tightened schemas.
- Before the implementation,
- Not verified: hosted deployment, the production database (migration not applied anywhere), live providers, real MCP clients and the chat UI.
Out of scope / known gaps
- Later ISRC, UPC or store-ID assignment and conflicting-ID handling are slice 2 of #2141.
planrejects these entries withUnsupported saved context entry(409) instead of returning a structured unimplemented plan like the songwriter/company/campaign/material entries. No collection module exists for them yet, and nothing is dispatched.- The database target reads (
list_context_*_request_target) are not allowlisted in the API until a planning or inspector slice needs them. - No chat or inspector UI (#2146).
- No audio or file intake (#2140).
- No composition, rights, ownership or roster linkage.
🤖 Generated with Claude Code
Summary by cubic
Accounts can now save unreleased recordings and planned releases before they have an ISRC, UPC, or store listing. Adds ingest_unreleased_recording and ingest_planned_release to the shared Context operation, exposed over HTTP POST /api/context and via the MCP tools import_unreleased_recording_context / import_planned_release_context.
- Strict Zod schemas reject
isrc,upc,store_ids, andspotify_idkeys before authorization, plus unknown lifecycle states, non-http(s) links, and more than 20 links; the database assigns an internal identity and records identifiers as unknown. - Nothing is dispatched, looked up, or charged. The
company_onboardingbrief recipe now includes both topics, so assessments show planned music with its explicit identifier gaps.
Migration
Deploy only after recoupable/database#111 is applied. Before 20261010214100_context_unreleased_music_entry.sql exists, the RPCs are missing and both operations return 409.