@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
697 lines • 44.5 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
export const REQUIRED_SCHEMAS_SOURCE = {
"work-required": {
workflow: "work-required",
summary: "Required fields to register a fully-credited musical work.",
primary_tool: "pica_works_create",
enum_tables: [{ table: "works", columns: ["work_type", "work_status"] }],
companion_calls: [
{
tool: "pica_credits_update",
when: "Once per work, batch — attach all writer/publisher splits in a single call.",
required: ["work_id", "credits"],
per_row_required: ["person_id", "role"],
constraint: "splits values must sum to 100 across all credits for the work (the tool maps role→work_collaborators/recording_credits by grain and splits→split_percentage).",
},
{
tool: "pica_credit_update",
when: "After credits exist — edit ONE credit in place (a writer's share, a role, the displayed name, notes, an instrument, or who it is for via person_id) without re-sending the set. Find the credit_id with pica_credits_list. The credit keeps its id; its attestation state is kept unless person_id changes who it is for, which sends a confirmed, disputed or declined credit back to pending.",
required: ["work_id", "credit_id"],
constraint: "At least one of share_pct, credit_type, credited_name, notes, instrument, person_id. share_pct is 0-100, or null to record the share as UNKNOWN (never 0). Writer-split closure is NOT enforced on a single edit — re-split one writer at a time. person_id must be a person in this organisation, not deleted, and not already holding this role on the song or recording (CREDIT_PERSON_CONFLICT); linking a name-only credit to a differently named person needs confirm_name_mismatch: true.",
},
{
tool: "pica_works_update",
when: "After creation — to amend title, work_type, work_status, iswc, lyrics, or any other inline field. Use the same payload shape as create with the addition of `id`.",
required: ["id"],
},
{
tool: "pica_works_attest",
when: "After registering — stamp a human-creation provenance attestation for sync libraries / provenance certificates. Captures who signed it, when, and the attestation method.",
required: ["id"],
},
{
tool: "pica_credits_send_for_attestation",
when: "After attaching credits — notify each credited person (in-app + telegram + email) that they have a pending credit awaiting their attestation. Lightweight alternative to pica_split_sheet_send when a signed document isn't needed.",
required: ["work_id"],
},
{
tool: "pica_works_verify_registrations",
when: "When registration status is unknown — mode 'check' looks works up at the MLC and records what the lookup PROVED (found = positive society fact; clean not-found = verified-absent assertion); mode 'confirm_unregistered' records the user's own confirmation. Up to 25 works per call; idempotent.",
required: ["work_ids", "mode"],
},
],
// ⚠️ NO lyrics_language HERE. This example is copied verbatim by agents, so
// a language on a work carrying no lyric sheet teaches them to assert one —
// the same invention removed from the column DEFAULT and the CSV import
// defaults on 2026-09-09 (migration 20260909120000, #2187), re-entering
// through the MCP write path that now creates most new works. An unknown
// language is stored as unknown. Pinned by "the work example asserts no
// lyrics_language" in required-schemas.test.ts.
example: {
title: "Midnight Rivers",
work_type: "song",
iswc: "T-123.456.789-0",
duration_seconds: 213,
},
},
"recording-required": {
workflow: "recording-required",
summary: "Required fields to register a recording (a specific master, version, or live capture of a work).",
primary_tool: "pica_recordings_create",
enum_tables: [{ table: "recordings", columns: ["version_type"] }],
companion_calls: [
{
tool: "pica_recording_credits_update",
when: "Per credit — attach a single MainArtist / Producer / Engineer credit (single form needs recording_id + credited_name + role). Also accepts a credits[] batch across release_id / recording_ids (b477cd175 write-merge) — see recording-credits-required for the batch shape. Multi-mode, so the schema-level required list is empty; mode requirements live in this prose.",
required: [],
},
{
tool: "pica_recordings_update",
when: "After creation — to amend recording_title, artist_name, version_type, isrc, duration_ms, or any other inline field. Same payload shape as create plus `id`.",
required: ["id"],
},
],
example: {
title: "Midnight Rivers (Master)",
artist_name: "The Riverbed Trio",
version_type: "master",
isrc: "USABC2300001",
duration_ms: 213000,
},
},
"work-credits-required": {
workflow: "work-credits-required",
summary: "Required fields per credit row when attaching writer/composer/publisher credits to a work. The tool exposes `role` (composition roles route to `work_collaborators`, master roles to `recording_credits`, keyed by grain) and `splits` (mapped to `split_percentage`). Splits must sum to 100. Valid role values are enumerated on the `role` field of `pica_credits_update`. Attestation is not a field you send: every credit starts as pending and only the credited person confirms it (pica_credits_send_for_attestation).",
primary_tool: "pica_credits_update",
companion_calls: [
{
tool: "pica_credit_update",
when: "After credits exist — edit ONE credit in place (a writer's share, a role, the displayed name, notes, an instrument, or who it is for via person_id) without re-sending the set. Find the credit_id with pica_credits_list. The credit keeps its id; its attestation state is kept unless person_id changes who it is for, which sends a confirmed, disputed or declined credit back to pending.",
required: ["work_id", "credit_id"],
constraint: "At least one of share_pct, credit_type, credited_name, notes, instrument, person_id. share_pct is 0-100, or null to record the share as UNKNOWN (never 0). Writer-split closure is NOT enforced on a single edit — re-split one writer at a time. person_id must be a person in this organisation, not deleted, and not already holding this role on the song or recording (CREDIT_PERSON_CONFLICT); linking a name-only credit to a differently named person needs confirm_name_mismatch: true.",
},
{
tool: "pica_credits_send_for_attestation",
when: "After attaching credits — send confirmation prompts to credited collaborators so each person can attest or dispute their own row. Lightweight alternative to pica_split_sheet_send when a signed split sheet isn't needed.",
required: ["work_id"],
},
],
example: {
work_id: "<uuid>",
credits: [
{ person_id: "<uuid>", role: "writer", splits: 50 },
{ person_id: "<uuid>", role: "writer", splits: 50 },
],
},
},
"recording-credits-required": {
workflow: "recording-credits-required",
summary: "Required fields per credit row when attaching MainArtist / FeaturedArtist / Producer / Engineer credits to a recording. Distinct role enum from work credits. person_id is optional — credits can carry just credited_name.",
primary_tool: "pica_recording_credits_update",
enum_tables: [{ table: "recording_credits", columns: ["role"] }],
// No companion calls. A deprecated alias used to be listed here with a
// "Never — call the canonical instead" note, purely to satisfy the
// forward direction of `scripts/lint-required-schemas.ts`: the alias is
// registered as a clone of its canonical, so it inherits the canonical's
// `workflows` tag and the lint demanded a reference. That made an
// agent-facing resource carry the exact name we are retiring. The lint's
// forward direction now skips alias names (`getAliasFor(name) !== undefined`
// — see `runChecks` there, and the "an alias never has to be listed"
// case in `scripts/__tests__/lint-required-schemas.test.ts`), so no
// resource has to name one.
companion_calls: [],
example: {
recording_id: "<uuid>",
credited_name: "The Riverbed Trio",
role: "MainArtist",
person_id: "<uuid>",
},
},
"person-required": {
workflow: "person-required",
summary: "Required fields to register a person (writer, performer, producer, publisher). Identifiers (IPI / ISNI / PRO / MusicBrainz / etc.) are inline-settable at create time.",
primary_tool: "pica_people_create",
enum_tables: [],
companion_calls: [
{
tool: "pica_people_update",
when: "After creation — to amend name, identifiers (IPI / ISNI / PRO / MusicBrainz / etc.), or any other inline field. Same payload shape as create plus `id`.",
required: ["id"],
},
],
example: {
first_name: "Jane",
last_name: "Doe",
cae_ipi_number: "00000000001",
isni: "0000000123456789",
pro_member_number: "PRS123456",
},
},
"audio-upload-required": {
workflow: "audio-upload-required",
summary: "Three-step chain to add an audio master / stem / version to the catalog: presign → PUT → finalize, then optional async analyze + poll.",
primary_tool: "pica_audio_presigned_upload",
enum_tables: [
{ table: "audio_files", columns: ["file_type", "classification"] },
],
companion_calls: [
{
tool: "pica_audio_complete_upload",
when: "Step 2 — after the uploader PUTs the file to the signed URL returned by step 1. Finalizes the upload, writes the audio_files row, and (optionally) links or auto-creates a recording for master files.",
required: [
"upload_id",
"key",
"bucket",
"filename",
"content_type",
"file_size",
],
},
{
tool: "pica_audio_analyze",
when: "Step 3 (optional, async). Triggers BPM / key / mood feature extraction (Librosa) and lyrics transcription (Whisper). Each side bills 1 credit. Does not auto-run on complete_upload — explicit call required.",
required: ["id"],
},
{
tool: "pica_audio_inspect",
when: "Step 4 (poll). Call with sections: ['analysis', 'status'] until the analysis job reports complete. Lyrics transcripts land on the transcriptions row and auto-copy to works.lyrics only when it is empty — existing lyrics are never overwritten.",
required: ["id"],
},
],
example: {
filename: "midnight-rivers-master.wav",
content_type: "audio/wav",
file_size: 48234567,
work_id: "<optional uuid — auto-creates a recording for master files>",
},
},
"enrichment-resolve-required": {
workflow: "enrichment-resolve-required",
summary: "Fan out enrichment across every eligible source for an entity (work / recording / person), then review fuzzy Tier B matches in the proposals queue. Tier A identifier matches apply directly; Tier B fuzzy matches queue as proposals.",
primary_tool: "pica_resolve_work",
enum_tables: [
{
table: "enrichment_proposals",
columns: ["status", "entity_type", "proposal_action"],
},
// The values a file-type question's answer may take
// (pica_enrichment_proposal_apply `file_type`).
{
table: "audio_files",
columns: ["file_type"],
},
],
companion_calls: [
{
tool: "pica_resolve_recording",
when: "Recording-side variant. Fans out across Spotify, YouTube, MusicBrainz, Discogs. Triggered by the canonical spotify_track_id / youtube_video_id on the recording.",
required: ["recording_id"],
},
{
tool: "pica_resolve_release",
when: "Release-side variant. Fans out across Spotify, MusicBrainz, Discogs. Triggered by trigger identifiers on the release (e.g. spotify_album_id, upc).",
required: ["release_id"],
},
{
tool: "pica_resolve_person",
when: "Person-side variant. Fans out across ISNI and MusicBrainz (Wikidata downstream). Triggered by isni / musicbrainz_id on the person.",
required: ["person_id"],
},
{
tool: "pica_enrichment_proposals_list",
when: "After resolve — list pending Tier B fuzzy matches awaiting review. Only pending status is returned.",
required: [],
},
{
tool: "pica_enrichment_proposal_apply",
when: "Commit a reviewed proposal. Drift detection runs first on update proposals — pass force=true only after the user has reviewed any conflicts from a prior drift_detected response. For a file-type question (entity_type audio_file) pass the user's chosen file_type (and, for a stem, an optional stem_label); ask them about each file, never pick for them.",
required: ["proposal_id"],
constraint: "file_type ∈ audio_files.file_type and only on entity_type audio_file; stem_label only with file_type stem, max 60 characters; a refused answer is INVALID_FILE_TYPE_ANSWER and writes nothing",
},
{
tool: "pica_enrichment_proposal_reject",
when: "Dismiss a proposal. Content-hash suppression permanently blocks re-proposal of the same exact content; cascade only re-proposes if the source data genuinely changes.",
required: ["proposal_id"],
},
{
tool: "pica_enrichment_proposals_bulk_reject",
when: "Dismiss up to 100 pending proposals in one call — by explicit proposal_ids OR a filter (rule_id / source / max_confidence). Per-proposal effects identical to the single reject (permanent content-hash suppression). Pass dry_run=true first to preview the real affected set.",
required: [],
constraint: "exactly one of proposal_ids or filter must be supplied; dry_run previews without mutating",
},
{
tool: "pica_enrichment_proposals_bulk_apply",
when: "Accept a reviewed batch — up to 100 pending proposals in one call, by EXPLICIT ids only. There is deliberately no filter form: a mis-filtered bulk reject costs a re-proposal, a mis-filtered bulk apply writes to the catalogue. Pass dry_run=true first to see what would land.",
required: ["proposal_ids"],
constraint: "proposal_ids only, max 100; partial success is the NORMAL shape — a non-empty failed[] beside a non-zero applied is expected, never report a partial as a success; dry_run=true returns { would_affect, not_pending } and writes nothing, and would_affect is an upper bound because drift is only detected at write time",
},
],
example: {
work_id: "<uuid>",
sources: ["mlc", "spotify"],
include_fuzzy: true,
},
},
"agreement-required": {
workflow: "agreement-required",
summary: "Required fields to create an agreement, then attach works and (optionally) render from a template.",
primary_tool: "pica_agreements_create",
enum_tables: [
{
table: "agreements",
columns: ["agreement_type", "other_party_type", "status"],
},
],
companion_calls: [
{
tool: "pica_agreements_update",
when: "After creation — to amend any inline field. Status changes trigger notifications; counterparty_org_id changes require the linked-org check at the route layer.",
required: ["id"],
},
{
tool: "pica_agreements_link_work",
when: "Attach a work to the agreement. Optionally set a royalty split percentage per work.",
required: ["agreement_id", "work_id"],
},
{
tool: "pica_agreement_templates_render",
when: "Template-driven flow — fill an agreement template with catalog data (work_id + people_ids) before creating the agreement row.",
required: ["id"],
},
{
tool: "pica_agreements_create_from_template",
when: "Template-driven flow with signers in one call — render a template and create the resulting agreement in draft status with required_signers already set. The only creation path that can set required_signers; pica_agreements_create has no such field.",
required: [
"template_id",
"title",
"agreement_type",
"required_signers",
"variables",
],
},
{
tool: "pica_agreements_send_for_signature",
when: "Send every required signer of a draft agreement a personal signing link by email.",
required: ["agreement_id"],
},
],
example: {
title: "Songwriter Admin Deal — Midnight Rivers",
agreement_type: "publishing_admin",
other_party_name: "Indigo Music Publishing Ltd.",
},
},
"multimedia-required": {
workflow: "multimedia-required",
summary: "Required fields to register a multimedia item (photo / video / audio / external streaming link), and the patterns for importing or attaching to a work.",
primary_tool: "pica_multimedia_create",
enum_tables: [
{
table: "multimedia_items",
columns: ["content_type", "classification"],
},
],
companion_calls: [
{
tool: "pica_multimedia_import_url",
when: "Import an external image or video URL — downloads the file into PICA storage rather than referencing it externally. Use for Spotify artwork, web images, etc.",
required: ["url"],
},
{
tool: "pica_multimedia_link_youtube",
when: "YouTube-specific variant — pulls thumbnail, title, and view count automatically. Use this instead of pica_multimedia_create for YouTube videos.",
required: ["youtube_video_id", "title"],
},
{
tool: "pica_multimedia_link_work",
when: "Attach an existing multimedia item to a work with a typed relationship (music_video_for, sync_placement, album_artwork, etc.).",
required: ["multimedia_id", "work_id", "relationship_type"],
},
],
example: {
title: "Midnight Rivers — Official Music Video",
content_type: "youtube_video",
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
},
},
"export-required": {
workflow: "export-required",
summary: "Generate exports of works in formats matching the destination — pica_export_catalog_csv emits per-work or per-batch industry submission files (pro / distributor / nro / sync-pdf), pica_export_song_registration bundles CSV/JSON/PDF plus a CWR field-mapping guide for PRO registration (a mapping guide, NOT a CWR file — PICA has no CWR emitter), pica_export_industry_ready packages metadata for publishers/labels/sync, pica_export_catalog_asset_report (CAR) bundles diligence/finance evidence, pica_export_ai_consent emits per-work + per-contributor AI declaration matrices. Four diligence documents complete the set: pica_export_snapshot (owner-facing, what to fix next), pica_export_ownership_record (the register of who owns what), pica_export_rights_proof (the evidence behind that register), pica_export_diligence_pack (readiness for registration or sale). For everything PICA holds about specific songs — recordings, releases, credits, people — pica_export_song_dossiers bundles one dossier per work into a ZIP.",
primary_tool: "pica_export_catalog_csv",
enum_tables: [],
companion_calls: [
{
tool: "pica_export_song_registration",
when: "Song-registration bundle for PRO/CMO submission — CSVs plus a CWR field-mapping guide, not a CWR file.",
required: [],
},
{
tool: "pica_export_industry_ready",
when: "Industry-ready metadata package — formatted for distribution to publishers, labels, and sync agents.",
required: [],
},
{
tool: "pica_export_catalog_asset_report",
when: "Catalog Asset Report (CAR) — unified ZIP bundle with cover PDF, evidence folders, integrity manifest. Property-lending framing for finance / insurance / diligence. Nested `sections` object accepts boolean flags: valuation, assets, agreements, intelligence. Ownership is always included. The report is unsigned: only someone signed in to PICA can attest it, from the export panel.",
required: [],
},
{
tool: "pica_export_ai_consent",
when: "AI-usage consent declaration matrix — per-work + per-contributor declarations of human-made / AI-assisted / AI-generated.",
required: [],
},
{
tool: "pica_export_snapshot",
when: "Catalogue snapshot for the OWNER deciding what to fix — PICA score per pillar, ownership coverage, gaps ranked by severity each with a suggested action, production-asset provenance, and a prioritised what-to-do-next list. Scope with scope='work'|'selected'|'everything'. For a lender/buyer-facing document use pica_export_catalog_asset_report instead.",
required: [],
},
{
tool: "pica_export_song_dossiers",
when: "Everything PICA holds about specific songs, as one ZIP: a song dossier (PDF + JSON) per work — the work, its recordings, the releases they appear on, people, provenance, consent. 1 to 60 work_ids; more is refused, not truncated. A file for the catalogue owner; it sends nothing to anyone.",
required: ["work_ids"],
},
{
tool: "pica_export_ownership_record",
when: "The ownership REGISTER — per-contributor and per-asset record of who owns what, with claim status, share percentages, contributor IPI/ISNI, and the definitions a lawyer needs to read it. Pair with pica_export_rights_proof, which is the evidence behind this register.",
required: [],
},
{
tool: "pica_export_rights_proof",
when: "The EVIDENCE behind the ownership register — timestamped claims, contributor attestations, identity verification, evidence-integrity hashes. Use when asked to PROVE ownership rather than state it.",
required: [],
},
{
tool: "pica_export_diligence_pack",
when: "Catalogue diligence pack as a document — readiness scoring, ownership clarity, identifier completeness, uninsured insurable assets, gaps ranked by severity with a suggested action. For the same analysis as JSON (cheaper, no document) call pica_catalog_diligence instead.",
required: [],
},
],
example: {
format: "pro",
work_id: "11111111-1111-1111-1111-111111111111",
},
},
"session-required": {
workflow: "session-required",
summary: "Required fields to log a studio session — title + start_time + end_time (ISO 8601 datetimes). Optional `session_type_id` is a UUID FK to the org's session_types lookup table; resolve via pica_sessions_types. The `status` column accepts scheduled / in_progress / completed / cancelled per the inputSchema, but the values are NOT enforced by a DB CHECK constraint — verify against the live tool's inputSchema if values matter to the caller.",
primary_tool: "pica_sessions_create",
enum_tables: [],
companion_calls: [
{
tool: "pica_sessions_types",
when: "Pre-flight to discover valid session_type_id values for this org. The lookup table is org-scoped and seeded with recording / mixing / mastering / writing / production / rehearsal entries on org bootstrap.",
required: [],
},
{
tool: "pica_sessions_inspect",
when: "After creation — verify the session row + participants persisted as expected. Reads back the full session record including any participants attached at create time. Pass id OR session_id (interchangeable entry points — requiredOneOf, so the schema-level required list is empty).",
required: [],
},
{
tool: "pica_sessions_query",
when: "Discovery / browse — list recent or upcoming sessions for the workspace.",
required: [],
},
],
example: {
title: "Vocal recording — Midnight Rivers",
start_time: "2026-05-08T14:00:00+01:00",
end_time: "2026-05-08T17:00:00+01:00",
session_type_id: "<uuid from pica_sessions_types>",
location: "Tileyard Studios",
work_id: "<optional uuid>",
},
},
"split-sheet-required": {
workflow: "split-sheet-required",
summary: "Required fields to generate a publishing split sheet on a work. Pre-condition: the work's writer credits (set via pica_credits_update) must already total 100% — pica_split_sheet_generate validates and returns 400 otherwise. The generated sheet snapshots current splits + signature state; pica_split_sheet_send notifies each credited writer for review/signature. The split_sheets.status column defaults 'draft' and is NOT enforced by a DB CHECK constraint.",
primary_tool: "pica_split_sheet_generate",
enum_tables: [],
companion_calls: [
{
tool: "pica_split_sheet_send",
when: "After generate — notify each credited writer for review/signature via in-app notification + Telegram (if connected) + email. Non-PICA-user writers receive a collaboration invite email instead. Soft-fails per-writer; aggregate result reports delivered / not_a_pica_user / failed counts.",
required: ["work_id", "split_sheet_id"],
},
{
tool: "pica_split_sheet_get",
when: "Read back a specific sheet with its full content + signature state. Use after send to check who has signed.",
required: ["work_id", "split_sheet_id"],
},
{
tool: "pica_split_sheet_list",
when: "List all sheets for a work — useful when multiple revisions exist (e.g. a sheet was generated, splits changed, regenerate).",
required: ["work_id"],
},
],
example: {
work_id: "<uuid — credits must total 100% first via pica_credits_update>",
},
},
"recording-splits-required": {
workflow: "recording-splits-required",
summary: "Required fields per row when assigning master-rights ownership on a recording. Distinct from publishing splits on works (those flow through pica_credits_update + pica_split_sheet_*). Recording splits drive master royalty payouts and downstream credit gating; pica_recording_splits_create runs an ADR-200 Class D form-confirmation gate when the client supports `elicitation/url`. Use `dry_run: true` first — it writes nothing and returns the shares already recorded plus `likely_refusal` when the write would be refused.",
primary_tool: "pica_recording_splits_create",
enum_tables: [
{ table: "recording_splits", columns: ["split_type", "role"] },
],
companion_calls: [
{
tool: "pica_recording_splits_verify",
when: "After create — explicitly mark a split as confirmed/agreed by the relevant parties. Sets verified=true + verified_at + verified_by on the row.",
required: ["recording_id", "split_id"],
},
{
tool: "pica_recording_splits_list",
when: "List all splits on a recording — verify totals and per-row verification state.",
required: ["recording_id"],
},
{
tool: "pica_recording_splits_update",
when: "A recorded share is wrong — relink a holder-less ('unknown') row to a person, or change its percentage, role, split_type, territory or dates. The 100% total is checked after the change with this row's old value left out. Changing who/how much/dates clears the holder's confirmation (verification_cleared).",
required: ["recording_id", "split_id"],
},
{
tool: "pica_recording_splits_end",
when: "A share should stop counting toward the total — sets end_date (default today) and keeps the row as history. Use instead of deleting.",
required: ["recording_id", "split_id"],
},
],
example: {
recording_id: "<uuid>",
split_type: "master",
percentage: 50,
role: "owner",
person_id: "<optional uuid>",
territory: "worldwide",
},
},
"claim-required": {
workflow: "claim-required",
summary: "Resolve pending discoveries (credits, custody, artist links) into your catalog. Each discovery type has a typed claim verb; the generic discoveries_review surface accepts/rejects without specialised handling.",
primary_tool: "pica_discoveries_query",
enum_tables: [
{
table: "discovered_credits",
columns: ["status", "match_basis"],
},
{
table: "discovered_custody",
columns: ["status", "custody_type"],
},
{
table: "discovered_artists",
columns: ["status"],
},
],
companion_calls: [
{
tool: "pica_discoveries_review",
when: "Generic accept/reject for any discovery row — accepted discoveries update your catalog automatically. Use for non-credit / non-custody / non-artist discoveries, or when the typed claim tools refuse on rate-limit / state preconditions.",
required: ["discovery_id", "status"],
},
{
tool: "pica_claim_credit",
when: "Mark a discovered_credits row as claimed — a status transition that grants view access to the linked work (no credit row is written). Rate-limited to 20 claims/hour. Re-claiming an already-resolved row returns DISCOVERY_ALREADY_RESOLVED (409).",
required: ["id"],
},
{
tool: "pica_claim_custody",
when: "Drain a discovered_custody row into a pending custody_claims row with +72h silent-consent auto-approve. The current custodian keeps the catalog row until the window elapses.",
required: ["id"],
},
{
tool: "pica_claim_artist",
when: "Drain a discovered_artists row instantly (identity evidence already validated at discovery time). Requires your identity to already be linked via pica_update_my_identity. Refuses (409) when an open artist_claims review row exists.",
required: ["id"],
},
],
example: {
status: "pending",
limit: 50,
},
},
"sync-placement-required": {
workflow: "sync-placement-required",
summary: "Sync-placement create flow — atomic write of (placement + draft sync_license agreement + at least one agreement_party + recordings + evidence sources) in a single Postgres transaction. At least one entry in `contacts[]` is required (empty rejects with structured MISSING_CONTACT). Contacts may reference an existing `person_id` OR pass `person_name` + `role` (+ optional email/company/position) to auto-create a person row inline. The verification_status auto-advances based on evidence kinds: any non-attestation source ('cue_sheet', 'invoice', 'agreement_pdf', 'email', 'screenshot', 'web', 'spreadsheet_import') → evidence_attached; only 'user_attestation' → self_attested; none → unverified. Operator-only fields (verification_status='operator_verified'|'disputed', confidentiality_level changes, hard delete) throw OPERATOR_REQUIRED for customer agents; use update with status:'terminated' for self-service retirement.",
primary_tool: "pica_sync_placements_create",
enum_tables: [
{
table: "sync_placements",
columns: ["status", "verification_status", "confidentiality_level"],
},
],
companion_calls: [
{
tool: "pica_sync_placements_inspect",
when: "After create — read the placement back to confirm verification_status auto-advance and inspect the linked agreement + parties + recordings + sources.",
required: ["id"],
},
{
tool: "pica_sync_placements_update",
when: "Patch fee, dates, status, territory, media_channels, brand, production_company, or any other inline field. Status transitions follow the placement state machine; illegal transitions throw INVALID_TRANSITION.",
required: ["id"],
},
{
tool: "pica_sync_placements_cite",
when: "Attach an evidence source after creation — auto-advances verification_status when the kind is non-attestation.",
required: ["id", "url", "kind"],
},
{
tool: "pica_sync_placements_delete",
when: "Operator-only hard delete (two-step confirmation). Customer agents should use pica_sync_placements_update with status:'terminated' instead for self-service soft-delete.",
required: ["id"],
},
{
tool: "pica_sync_placements_query",
when: "List placements for the org — filter by work_id, status, verification_status, brand, production_company, person_id, recording_id, source_license_enquiry_id, min_fee, currency.",
required: [],
},
],
example: {
work_id: "00000000-0000-0000-0000-000000000000",
production_title: "Midnight Rivers — Trailer 2026",
production_type: "trailer",
brand: "Lumière Studios",
production_company: "Lumière Studios Inc.",
status: "licensed",
fee_amount: 25000,
fee_currency: "USD",
use_start_date: "2026-06-01",
use_end_date: "2027-05-31",
territory: ["WW"],
media_channels: ["theatrical", "online"],
confidentiality_level: "redacted_in_public_car",
contacts: [
{
person_name: "Sarah Lee",
role: "music_supervisor",
email: "sarah@lumiere.example",
company: "Lumière Studios",
},
],
recordings: [
{
recording_id: "00000000-0000-0000-0000-000000000001",
role: "main_master",
master_fee: 12500,
master_share_licensed_pct: 100,
},
],
sources: [
{
url: "https://example.com/agreements/midnight-rivers.pdf",
kind: "agreement_pdf",
field_coverage: ["fee_amount", "use_start_date", "territory"],
},
],
},
},
// ADR-333 — who looks after whose catalogue. Every step sends the
// version the caller read; the terms are read out to the person first.
"relationship-required": {
workflow: "relationship-required",
summary: "Who looks after a catalogue (ADR-333). An owner invites an organisation by email with pica_relationships_grant; an organisation asks a client by email with pica_portfolio_invite. The other side answers with pica_relationships_answer: accept (optionally narrowing — never widening — type or scope), decline, or edit (a counter-proposal, or the owner's change to an active relationship). Every step on an existing relationship sends expected_version, the version read from pica_relationships_list or pica_portfolio_list; if the terms moved since, nothing happens and STATUS_CHANGED returns the current terms to read out again. Types: administrator (edits directly; splits and custody go to the owner), representative (every change goes to the owner as a proposal), viewer (sees and exports), co_owner (sees its share). Scope fields: half (both | composition | master), territories (only WW, worldwide, today: a limit to some countries is refused until a route can honour it), term_starts_on / term_ends_on (YYYY-MM-DD or null), money, directory (administrator only), catalogue (whole | chosen) with work_ids / recording_ids. Accepting, changing, revoking and an owner's invitation are the person's own decision: from a connected app they become an approval for the person; from PICA's own assistant they return HUMAN_CONFIRMATION_REQUIRED with the screen to do it on.",
primary_tool: "pica_relationships_grant",
companion_calls: [
{
tool: "pica_portfolio_invite",
when: "An organisation asks a client to let it look after their catalogue. Scope takes half, territories and term only: the client chooses what it covers when they accept.",
required: ["email", "type"],
},
{
tool: "pica_relationships_list",
when: "Before any step: read the relationship, its plain-words scope and its version; also lists invitations waiting for the person (invite_id + answer_for_one_of).",
required: [],
},
{
tool: "pica_relationships_answer",
when: "Answer a request: relationship_id + expected_version + action; or invite_id + organisation_id to accept an invitation by email (invitations can't be declined; they lapse after 30 days).",
required: [],
constraint: "Either relationship_id + expected_version + action, or invite_id + organisation_id. accept may only narrow type or scope; decline takes only a reason; edit needs a type or scope. With invite_id, action is accept and organisation_id is one of answer_for_one_of.",
},
{
tool: "pica_relationships_revoke",
when: "The owner ends a relationship (or withdraws its own request). Irreversible; takes effect on the organisation's next request.",
required: ["relationship_id", "expected_version"],
},
{
tool: "pica_relationships_step_down",
when: "The organisation ends its own side (or withdraws its own request). Irreversible.",
required: ["relationship_id", "expected_version"],
},
{
tool: "pica_relationships_activity",
when: "What was done through a relationship: one relationship by relationship_id, or across the catalogue (side owner) or the organisation's clients (side organisation).",
required: [],
},
{
tool: "pica_portfolio_list",
when: "An organisation's clients: each catalogue id to pass as `catalogue` to the catalogue tools, what a change does there (direct, proposal, none), and the version for stepping down.",
required: [],
},
{
tool: "pica_portfolio_briefing",
when: "The same clients with up to five things each that need attention within scope.",
required: [],
},
],
example: {
email: "manager@example.com",
type: "administrator",
scope: {
half: "composition",
territories: ["WW"],
term_ends_on: "2027-12-31",
money: false,
catalogue: "whole",
},
},
},
"handover-required": {
workflow: "handover-required",
summary: "A catalogue an organisation builds for a client, who accepts to own it (ADR-333 D5). The organisation creates it with pica_handover_create (nothing is sent; while it is being prepared it can enter facts, but nothing is published, agreed or signed in its favour), then sends it with pica_handover_send, which emails the client; they have 60 days to accept. relationship_type is what the organisation keeps once they accept: administrator, representative or viewer, over the whole catalogue. Sending is the person's own decision: from a connected app it becomes an approval for the person; otherwise PICA may return HUMAN_CONFIRMATION_REQUIRED with the screen to send it on. Accepting is the client's own act, signed in with the address it was sent to: they keep or remove each item that names the organisation, then accept or decline. No tool accepts or declines; pica_handover_answer gives the client the link.",
primary_tool: "pica_handover_create",
companion_calls: [
{
tool: "pica_handover_send",
when: "When the catalogue is ready: emails the client and starts the 60 days.",
required: ["handover_id"],
constraint: "expected_version is optional; when given and the hand-over changed since, nothing is sent.",
},
{
tool: "pica_handover_answer",
when: "The client asks how to take over a catalogue prepared for them. Returns the screen link only; it never accepts or declines.",
required: [],
},
],
example: {
catalogue_name: "Alice Example",
client_name: "Alice Example",
client_email: "alice@example.com",
relationship_type: "administrator",
},
},
};
export const REQUIRED_SCHEMA_WORKFLOWS = Object.keys(REQUIRED_SCHEMAS_SOURCE);
//# sourceMappingURL=required-schemas.source.js.map