Add single-video YouTube locator entry for Context
Summary
Adds public.create_context_video_request(p_owner uuid, p_actor uuid, p_video text, p_key text), the database half of a provider-independent, zero-spend ingest_video entry.
It saves one YouTube video (validated 11-character ID) as a video subject backed by a youtube/video resource, with:
- an accepted identity link whose only evidence is the submitted URL
- a
customersource and version snapshot - a zero-cost
user_submissionattempt - an accepted
customer_assertionresult on avideo_locatordocument
The request ends partial with six distinct gaps:
video_metadata: uncollecteduploader_attribution: unresolved (the uploading channel is never assumed to be the artist, a performer or a credited artist)recording_match: unresolvedcaptions: uncollected (no lyrics inferred)media_version: unknownvideo_narrative: unavailable
It creates no account, roster, song, song-identifier, artist, recording or release row and writes no song or artist document. This delivers, as persisted and tested contracts:
- the "remains explicitly unresolved" half of acceptance item 1
- the no-silent-lyrics guarantee of item 2
- the no-overwrite guarantee of item 3
No constraint changes: video was already permitted by the foundation and by every later constraint rewrite. So this migration cannot collide with sibling migrations that widen constraints.
Refs recoupable/app#2131 (child of epic recoupable/app#2116).
Merge sequencing
docs → database → api → app. Sibling PRs for this ticket, all on branch claude/ctx-2131-youtube-video-entry:
- recoupable/docs#366: OpenAPI block
- recoupable/api#1019: the
ingest_videoaction, which calls this function
Release this migration before the API PR; until then, the API action fails with its generic 409. No dependency on the open database PRs (#88/#89/#90/#92/#94/#95 are untouched). CI runs in a new workflow file (context-video-entry.yml, PostgreSQL 15/16) instead of editing release-cases.yml.
Verification
- Ran:
export PATH="/opt/homebrew/opt/postgresql@17/bin:$PATH" LC_ALL=C && python3 -m unittest discover -s tests -p 'test_context_video_entry.py'→ Ran 3 tests, OK. This uses PostgreSQL 17 (Homebrew) on a disposable cluster, Unix socket only. The fixture covers:- invalid identifiers (short, long, spaced, empty, null, full URL)
- save →
partialwith one video subject and six gaps - resource, subject and identity-link shape; no credited-artist or release-member link
- result and attempt shape: customer assertion, zero confirmed cost, no title/channel/lyrics keys
- exactly one
video_locatordocument, no song or artist documents, andread_context_documents - zero account, roster, song, identifier, artist, recording or release rows
- same-key replay without new rows, and a key conflict
- re-submitting the same video in the same workspace under a new key: the subject, resource, source and document are reused; exactly one request, attempt and accepted result are appended; the single
video_locatordocument advances one revision; no artist, song or non-locator rows appear - a second video as a distinct subject, leaving the first document untouched
- another workspace reading null, and the same video saved separately per workspace
- source withdrawal hiding the locator in one workspace only
- anon/authenticated denied while service_role can execute
- The lifecycle test now re-counts resources, requests, subjects, sources, results, documents, accounts, roster, songs and identifiers after the rolled-back fixture and expects 0. The earlier standalone count test ran alphabetically before the fixture, so it could not catch a leak. The test count drops from 4 to 3 as a result.
- Regression:
test_release_cases.py→ 1 test OK;test_company_assessment.py→ 3 tests OK. - Re-run on head 23262f9 (finalizer pass, PostgreSQL 17.7,
LC_ALL=C): Ran 3 tests, OK. To show the new re-submission check is not vacuous, I temporarily expected the revision to stay unchanged; the lifecycle test then failed withResubmission did not advance the single locator document. I reverted that change. - Red → green: with the new migration removed from the chain, the lifecycle test fails with
function public.create_context_video_request(uuid, uuid, text, unknown) does not exist. It passes with the migration. grep -c 'grant execute on function public.create_context_video_request'→ 1; therevoke all … from public,anon,authenticatedline is present.- PR CI passed
context-video-entryfor PostgreSQL 15 and 16 on 718a36f and again on 23262f9, which includes the re-submission case. The other database workflows also passed on 23262f9. - Not verified:
- production database: the migration has not been applied to production. Supabase's GitHub integration applied it automatically to this PR's ephemeral preview branch when the PR opened, and its Supabase Preview check passed. That preview branch was not exercised.
- PostgreSQL 15/16 locally (the CI workflow covers them)
- live providers (none are called)
- an end-to-end API/MCP round trip through a deployed service
Out of scope / known gaps
- Not included: metadata verification (title/channel), retention of captions/description/language, recording or artist attribution, or a
list_context_video_request_targetfor a future default-offverify_videostep. These are the named follow-ups in the API README. plandoes not supportkind: 'video'; the API rejects it as an unsupported saved entry rather than silently planning research.- recoupable/app#2130 (CE14 checkpoint) still gates ticket completion. This is contract and fixture work, which the epic permits before that checkpoint.
- Implemented and fixture-tested only; not connected, deployed or live-verified.
🤖 Generated with Claude Code