Add ingest_video Context action for one YouTube video locator
Summary
Adds the provider-independent, zero-spend ingest_video action to the shared Context operation used by HTTP POST /api/context, the MCP context tool and the OAuth catalog (import_music_video_context). parseContextVideoUrl canonicalizes exactly one YouTube video URL (youtu.be, watch?v=, shorts, embed; tracking parameters dropped) and points Spotify tracks to ingest. The strict Zod schema rejects Spotify tracks and albums, playlists, channels, multi-v, http and lookalike hosts before authorization or storage. It accepts no topics or direction, because nothing is collected. After the existing authorizeContextOwner step, processVideoOperation calls create_context_video_request once with the workspace owner and returns the saved partial request (HTTP 200). It dispatches no workflow, calls no provider and spends no credits. The saved record keeps uploader/channel, performers, credited artists, recording, captions, language and media version as explicit unresolved/uncollected/unknown gaps. It never attaches to a song or artist document. ingest stays Spotify-only; its rejection message now points YouTube URLs at ingest_video.
Guard tests (videoLocatorBoundaries.test.ts) pin two boundaries. First, plan rejects a saved video entry before reading targets, so no collection is planned. Second, creative-direction, playlist-pitch and company-onboarding briefs leave the video_locator document out of song and artist evidence: it is listed only in input_manifest.excludedTopics, and the video request is reported as partial.
Plan adjustments found while validating against the code:
callContextRpc.tskeeps an RPC allowlist, so the function name is added there.contextToolOperations.tsissatisfies Record<Action, …>, andcontextToolOperations.test.tsasserts every schema action has a tool, soimport_music_video_contextis added.fullOAuthCatalog.test.tspins the delegated tool count (84 → 85).
Refs recoupable/app#2131 (child of epic recoupable/app#2116).
Merge sequencing
docs → database → api → app. Depends on recoupable/database#107 (migration 20261010213100_context_video_entry.sql) being released first; until then, ingest_video fails with the generic 409. Sibling recoupable/docs#366 documents the action. All three use branch claude/ctx-2131-youtube-video-entry. No dependency on open API PRs (#981/#983/#985/#986–#991/#996).
Expected overlap on rebase:
- api#1001 and api#1014 (both open) also edit
lib/context/README.md; this PR inserts one bullet afteringest_release. - The
ingestrejection string inprocessContextOperation.tschanged on one line (theingestbranch belongs to #2126). lib/mcp/oauth/__tests__/fullOAuthCatalog.test.tspins the delegated tool count. This PR, api#1017 and api#1004 each change it from 84 to 85, and api#1023 changes it to 86. Whichever merges later must bump the count again to the real total.
No chat change.
Verification
- Ran:
pnpm exec vitest run lib/context/__tests__/videoOperation.test.ts lib/context/__tests__/videoLocatorBoundaries.test.ts→ 2 files, 22 passed, 0 failed.videoOperation.test.ts(18): canonicalization; eight rejected non-video inputs; onecreate_context_video_requestRPC with the authorized owner (organization and personal scope); no dispatch; rejection before authorize/RPC with the exacturlrefinement issue; strict rejection oftopics/directionwith the exactunrecognized_keysissue;ingeststill rejects YouTube and namesingest_video.videoLocatorBoundaries.test.ts(4):planfails closed; three brief purposes excludevideo_locator.
pnpm exec vitest run lib/context→ Test Files 69 passed | 8 skipped (77); Tests 348 passed | 8 skipped (356); 0 failed.pnpm build:extension && pnpm exec vitest run lib/mcp/oauth lib/mcp/tools/context lib/mcp/extension lib/supabase/context_requests→ 23 files, 91 passed, 0 failed.build:extensionis the esbuild steppnpm testalready runs. It generates the gitignoredgenerated-ui.ts; without it,fullOAuthRouting.test.tscannot load.pnpm exec tsc --noEmit→ 198 pre-existing errors, all in unrelated test files (trigger, chat, credits, composio, …). With this PR's changes reverse-applied, origin/main produces the identical sorted error list, so this PR adds 0 errors.pnpm exec eslintandpnpm exec prettier --checkon the ten changed/new TypeScript files andlib/context/README.md→ clean.- Red → green, two steps:
videoOperation.test.tsfailed before implementation withCannot find module '../parseContextVideoUrl'.- With origin/main's
processContextOperation.tsrestored (no schema spread, no routing), 5 of 18 fail on head ce28c44: organization-scoped save, personal-scope save, rejection of non-video URLs, rejection oftopics/direction, andingestnamingingest_video. All 18 pass with the change. (Before ce28c44 the two rejection tests only asserted that something was thrown, so they also passed without the action; only 3 of 18 failed.) - The boundary tests characterize existing behavior, so they were green from the start. A mutation adding
video_locatorto the creative-direction recipe makes the creative-direction case fail.
grep -n create_context_video_request lib/context/processVideoOperation.ts→ present; noyt-dlp/apify/googleapisreference in the new files.- Re-run on head ce28c44 (finalizer pass):
pnpm exec vitest run lib/context→ Test Files 69 passed | 8 skipped (77); Tests 348 passed | 8 skipped (356); 0 failed.pnpm exec tsc --noEmit→ 198 errors, none inlib/context,lib/mcp/oauth,lib/mcp/tools/context,lib/supabase/context_requestsor any changed file. eslint and prettier on the ten changed TypeScript files andlib/context/README.md→ clean. The MCP/OAuth suites above were not re-run, because onlyvideoOperation.test.tschanged. - Not verified: hosted deployment, live providers (none are called by design), production database, and an authenticated HTTP/MCP round trip against a deployed service.
Out of scope / known gaps
- Not built here:
- a default-off
verify_videometadata step (candidate zero-cost source: public YouTube oEmbed for title/channel only) and its commit RPC - captions, description and language retention
plansupport forkind: 'video'(today it throws "Unsupported saved context entry")- artist-research reuse via
createContextReuseKey - attribution review that links a related video to an existing artist or recording
- chat UI
- a default-off
- The
ingestredirect toingest_videoreaches callers only in-process.contextIngestSchemastill accepts a YouTube URL, soingestwith one passes validation and authorization and then throws.contextOperationHandler.tsmaps every thrown error to a generic 409, andregisterContextTool.tsreturns a generic tool error, so HTTP and MCP clients do not see the message. Typed transport errors are pre-existing scope (api#1005 / recoupable/app#2125). This PR does not change theingestschema, which belongs to #2126. - Briefs do not select
video_locatorevidence for any purpose. Video narrative, lyrics and brand content do not exist yet, so there is nothing to merge. - recoupable/app#2130 (CE14 checkpoint) still gates ticket completion.
- Implemented and fixture-tested only; not connected, deployed or live-verified.
🤖 Generated with Claude Code