@withpica/mcp-server 2.97.5 → 2.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +223 -0
- package/README.intro.md +1 -1
- package/README.md +8 -4
- package/dist/lib/changelog.generated.d.ts +2 -2
- package/dist/lib/changelog.generated.d.ts.map +1 -1
- package/dist/lib/changelog.generated.js +2 -2
- package/dist/lib/changelog.generated.js.map +1 -1
- package/dist/prompts/creator-question-atlas.d.ts.map +1 -1
- package/dist/prompts/creator-question-atlas.js +37 -2
- package/dist/prompts/creator-question-atlas.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +18 -21
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +3 -1
- package/dist/resources/agent-guide.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +0 -3
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/required-schemas.generated.d.ts +7 -5
- package/dist/resources/required-schemas.generated.d.ts.map +1 -1
- package/dist/resources/required-schemas.generated.js +15 -2
- package/dist/resources/required-schemas.generated.js.map +1 -1
- package/dist/resources/required-schemas.source.d.ts.map +1 -1
- package/dist/resources/required-schemas.source.js +8 -1
- package/dist/resources/required-schemas.source.js.map +1 -1
- package/dist/server-instructions.d.ts +2 -2
- package/dist/server-instructions.d.ts.map +1 -1
- package/dist/server-instructions.js +1 -1
- package/dist/skills/skills.generated.js +11 -11
- package/dist/skills/skills.generated.js.map +1 -1
- package/dist/tools/agreement-types.d.ts.map +1 -1
- package/dist/tools/agreement-types.js +3 -1
- package/dist/tools/agreement-types.js.map +1 -1
- package/dist/tools/agreements.d.ts.map +1 -1
- package/dist/tools/agreements.js +15 -6
- package/dist/tools/agreements.js.map +1 -1
- package/dist/tools/approvals.d.ts.map +1 -1
- package/dist/tools/approvals.js +14 -1
- package/dist/tools/approvals.js.map +1 -1
- package/dist/tools/arg-aliases.d.ts.map +1 -1
- package/dist/tools/arg-aliases.js +12 -0
- package/dist/tools/arg-aliases.js.map +1 -1
- package/dist/tools/audio-files.d.ts +39 -0
- package/dist/tools/audio-files.d.ts.map +1 -1
- package/dist/tools/audio-files.js +253 -12
- package/dist/tools/audio-files.js.map +1 -1
- package/dist/tools/catalog-enums.d.ts +20 -0
- package/dist/tools/catalog-enums.d.ts.map +1 -0
- package/dist/tools/catalog-enums.js +28 -0
- package/dist/tools/catalog-enums.js.map +1 -0
- package/dist/tools/collaborators.d.ts.map +1 -1
- package/dist/tools/collaborators.js +27 -3
- package/dist/tools/collaborators.js.map +1 -1
- package/dist/tools/credits.d.ts +4 -1
- package/dist/tools/credits.d.ts.map +1 -1
- package/dist/tools/credits.js +70 -97
- package/dist/tools/credits.js.map +1 -1
- package/dist/tools/custody-hints.js +2 -2
- package/dist/tools/custody-hints.js.map +1 -1
- package/dist/tools/custody.d.ts.map +1 -1
- package/dist/tools/custody.js +4 -1
- package/dist/tools/custody.js.map +1 -1
- package/dist/tools/deprecations.d.ts +67 -8
- package/dist/tools/deprecations.d.ts.map +1 -1
- package/dist/tools/deprecations.js +98 -82
- package/dist/tools/deprecations.js.map +1 -1
- package/dist/tools/directory.js +2 -2
- package/dist/tools/directory.js.map +1 -1
- package/dist/tools/discovery.d.ts +28 -2
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +72 -28
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/documents.d.ts +2 -2
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +2 -88
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/duplicates.d.ts +2 -0
- package/dist/tools/duplicates.d.ts.map +1 -1
- package/dist/tools/duplicates.js +173 -18
- package/dist/tools/duplicates.js.map +1 -1
- package/dist/tools/enrichment.d.ts.map +1 -1
- package/dist/tools/enrichment.js +36 -2
- package/dist/tools/enrichment.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +19 -0
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +44 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/integrity.d.ts.map +1 -1
- package/dist/tools/integrity.js +65 -8
- package/dist/tools/integrity.js.map +1 -1
- package/dist/tools/licensing.d.ts.map +1 -1
- package/dist/tools/licensing.js +14 -9
- package/dist/tools/licensing.js.map +1 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/metadata.js +74 -114
- package/dist/tools/metadata.js.map +1 -1
- package/dist/tools/notes.d.ts +3 -4
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +31 -44
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/people.d.ts +5 -1
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +8 -1
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/projects.d.ts +3 -4
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +3 -27
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/public-filter.d.ts.map +1 -1
- package/dist/tools/public-filter.js +3 -6
- package/dist/tools/public-filter.js.map +1 -1
- package/dist/tools/purchases.d.ts +2 -3
- package/dist/tools/purchases.d.ts.map +1 -1
- package/dist/tools/purchases.js.map +1 -1
- package/dist/tools/recording-attribution-hints.js +1 -1
- package/dist/tools/recording-attribution-hints.js.map +1 -1
- package/dist/tools/recording-custody.d.ts +3 -1
- package/dist/tools/recording-custody.d.ts.map +1 -1
- package/dist/tools/recording-custody.js +8 -4
- package/dist/tools/recording-custody.js.map +1 -1
- package/dist/tools/recovery-hints.d.ts +0 -6
- package/dist/tools/recovery-hints.d.ts.map +1 -1
- package/dist/tools/recovery-hints.js +97 -38
- package/dist/tools/recovery-hints.js.map +1 -1
- package/dist/tools/register.d.ts.map +1 -1
- package/dist/tools/register.js +2 -1
- package/dist/tools/register.js.map +1 -1
- package/dist/tools/releases.d.ts +2 -4
- package/dist/tools/releases.d.ts.map +1 -1
- package/dist/tools/releases.js +12 -213
- package/dist/tools/releases.js.map +1 -1
- package/dist/tools/rights.d.ts +54 -0
- package/dist/tools/rights.d.ts.map +1 -0
- package/dist/tools/rights.js +495 -0
- package/dist/tools/rights.js.map +1 -0
- package/dist/tools/royalties.d.ts +29 -6
- package/dist/tools/royalties.d.ts.map +1 -1
- package/dist/tools/royalties.js +101 -119
- package/dist/tools/royalties.js.map +1 -1
- package/dist/tools/section-validation.d.ts +14 -0
- package/dist/tools/section-validation.d.ts.map +1 -0
- package/dist/tools/section-validation.js +68 -0
- package/dist/tools/section-validation.js.map +1 -0
- package/dist/tools/selections.d.ts +5 -2
- package/dist/tools/selections.d.ts.map +1 -1
- package/dist/tools/selections.js +10 -4
- package/dist/tools/selections.js.map +1 -1
- package/dist/tools/share-links.d.ts.map +1 -1
- package/dist/tools/share-links.js +32 -6
- package/dist/tools/share-links.js.map +1 -1
- package/dist/tools/share-send.d.ts.map +1 -1
- package/dist/tools/share-send.js +32 -7
- package/dist/tools/share-send.js.map +1 -1
- package/dist/tools/signed-split-sheet-preview.d.ts +43 -0
- package/dist/tools/signed-split-sheet-preview.d.ts.map +1 -0
- package/dist/tools/signed-split-sheet-preview.js +67 -0
- package/dist/tools/signed-split-sheet-preview.js.map +1 -0
- package/dist/tools/split-sheets.d.ts.map +1 -1
- package/dist/tools/split-sheets.js +3 -2
- package/dist/tools/split-sheets.js.map +1 -1
- package/dist/tools/statements.js +1 -1
- package/dist/tools/statements.js.map +1 -1
- package/dist/tools/version-guard.d.ts +16 -4
- package/dist/tools/version-guard.d.ts.map +1 -1
- package/dist/tools/version-guard.js +22 -8
- package/dist/tools/version-guard.js.map +1 -1
- package/dist/tools/works.d.ts +9 -1
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +217 -74
- package/dist/tools/works.js.map +1 -1
- package/dist/tools/write-receipt.d.ts +68 -0
- package/dist/tools/write-receipt.d.ts.map +1 -0
- package/dist/tools/write-receipt.js +64 -0
- package/dist/tools/write-receipt.js.map +1 -0
- package/package.json +4 -4
- package/scripts/build-required-schemas.ts +7 -1
- package/server.json +2 -2
package/dist/tools/works.js
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
import { ApiError, EntityNotFoundError, } from "@withpica/mcp-sdk";
|
|
22
22
|
import { formatSuccess, formatDeletion, formatStructuredList, withBillingGate, buildDecision, mapGapsToHints, pushSectionError, } from "@withpica/mcp-utils";
|
|
23
23
|
import { buildCustodyHints } from "./custody-hints.js";
|
|
24
|
+
import { typedError } from "./error-envelope.js";
|
|
24
25
|
import { computeWorkGapHints } from "./gap-hints.js";
|
|
25
26
|
import { buildIntegrityPressure } from "./integrity-hints.js";
|
|
26
27
|
import { detectVersionOfExistingWork, buildVersionWarning, parseVersionTitle, } from "./version-guard.js";
|
|
28
|
+
import { WORK_TYPE_VALUES } from "./catalog-enums.js";
|
|
29
|
+
import { VERBOSE_PROPERTY, RECEIPT_NOTE, changedFields, newHintsOnly, slimIntegrity, takeVerbose, } from "./write-receipt.js";
|
|
27
30
|
/**
|
|
28
31
|
* Canonical write-schema for `works`. Every property below must
|
|
29
32
|
* correspond to a real column on `public.works` — verified against
|
|
@@ -41,7 +44,7 @@ export const WORK_WRITE_PROPERTIES = {
|
|
|
41
44
|
work_type: {
|
|
42
45
|
type: "string",
|
|
43
46
|
description: "Type of work",
|
|
44
|
-
enum: [
|
|
47
|
+
enum: [...WORK_TYPE_VALUES],
|
|
45
48
|
},
|
|
46
49
|
work_status: {
|
|
47
50
|
type: "string",
|
|
@@ -120,6 +123,22 @@ export const WORK_WRITE_PROPERTIES = {
|
|
|
120
123
|
type: "string",
|
|
121
124
|
description: "Primary artist person ID (FK to people)",
|
|
122
125
|
},
|
|
126
|
+
disambiguation: {
|
|
127
|
+
type: ["string", "null"],
|
|
128
|
+
maxLength: 200,
|
|
129
|
+
description: "A short note that tells apart two DIFFERENT songs with the same title and primary artist — e.g. 'theme', '2019 song', 'live-only composition' (MusicBrainz's term). " +
|
|
130
|
+
"Set it ONLY when two genuinely different songs share title + artist; the cue is a WORK_ALREADY_EXISTS refusal for a song you know is not the existing one. " +
|
|
131
|
+
"Leave it unset otherwise — it is part of the duplicate check, so an invented note would let a real duplicate in. " +
|
|
132
|
+
"If the two are the same song, use or merge the existing work instead. Not a version label: recording versions (live, remix, acoustic) belong on pica_recordings_create. " +
|
|
133
|
+
"null or '' clears it.",
|
|
134
|
+
},
|
|
135
|
+
original_work_id: {
|
|
136
|
+
type: ["string", "null"],
|
|
137
|
+
description: "Set ONLY on a MODIFIED VERSION: the id of the work this one modifies (CWR version type MOD). " +
|
|
138
|
+
"A remix is by default NOT a separate work — it is a new recording of the same song (pica_recordings_create with work_id of the original and version_type 'remix', remixer credited on the recording). " +
|
|
139
|
+
"Only when the remix adds writers the original does not have (new lyrics, melody or section) is it its own work with its own ISWC and writer shares; then set original_work_id to the original's id. " +
|
|
140
|
+
"Must be a work in the same catalogue, not this work. null clears the link.",
|
|
141
|
+
},
|
|
123
142
|
display_artist: {
|
|
124
143
|
type: "string",
|
|
125
144
|
description: "Display-friendly artist string (e.g. 'Jane Doe ft. John Smith')",
|
|
@@ -142,7 +161,7 @@ export const WORK_WRITE_PROPERTIES = {
|
|
|
142
161
|
},
|
|
143
162
|
notes: {
|
|
144
163
|
type: "string",
|
|
145
|
-
description: "Internal notes",
|
|
164
|
+
description: "Internal notes. Setting this REPLACES the whole field — to add a line, use append_notes on pica_works_update instead.",
|
|
146
165
|
},
|
|
147
166
|
metadata: {
|
|
148
167
|
type: "object",
|
|
@@ -200,6 +219,14 @@ function summariseDossier(dossier) {
|
|
|
200
219
|
`Consent: ${consentState}${consentLevel}. Completeness: ${completeness}. ` +
|
|
201
220
|
`(Full payload under structuredContent.data.dossier; PDF / multi-work ZIP: pica_export_song_dossiers.)`);
|
|
202
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Page caps for the two health_filter branches of pica_works_query — the
|
|
224
|
+
* routes' own maxima (app/api/admin/quality/works-health/route.ts and
|
|
225
|
+
* .../completeness/low-score/route.ts). Hand-copied (this package cannot
|
|
226
|
+
* import the app); no test ties them — change both together.
|
|
227
|
+
*/
|
|
228
|
+
const WORKS_HEALTH_MAX_PAGE = 50;
|
|
229
|
+
const LOW_COMPLETENESS_MAX_PAGE = 200;
|
|
203
230
|
export class WorksTools {
|
|
204
231
|
pica;
|
|
205
232
|
constructor(pica) {
|
|
@@ -220,7 +247,7 @@ export class WorksTools {
|
|
|
220
247
|
"pending registration?', 'unregistered works', 'works registered " +
|
|
221
248
|
"with ASCAP', 'least-complete works', 'works ready for outreach', " +
|
|
222
249
|
"'works with no statement data', 'most-streamed works'. Search " +
|
|
223
|
-
"by title, ISWC, or artist, or filter by
|
|
250
|
+
"by title, ISWC, or artist, or filter by " +
|
|
224
251
|
"registration status, the collection society a work is registered " +
|
|
225
252
|
"with (society_code, any territory), health, whether a collecting-" +
|
|
226
253
|
"society statement is linked (has_statement_data), completeness range " +
|
|
@@ -247,16 +274,12 @@ export class WorksTools {
|
|
|
247
274
|
properties: {
|
|
248
275
|
query: {
|
|
249
276
|
type: "string",
|
|
250
|
-
description: "Search by title, ISWC, or artist name",
|
|
251
|
-
},
|
|
252
|
-
verified: {
|
|
253
|
-
type: "boolean",
|
|
254
|
-
description: "Filter by verification status",
|
|
277
|
+
description: "Search by title, ISWC, or artist name. An artist name also finds works linked to that artist through a recording's artist or a credited person, even when the work's primary_artist is empty or spelled differently (appended after the direct matches, up to 300).",
|
|
255
278
|
},
|
|
256
279
|
health_filter: {
|
|
257
280
|
type: "string",
|
|
258
281
|
enum: ["needs_attention", "low_completeness"],
|
|
259
|
-
description: "Filter by health status — needs_attention (cross-org reconciliation problems: unreconciled composition/master chains, split discrepancies) or low_completeness (below completeness score threshold: missing ISWC, credits, etc.)",
|
|
282
|
+
description: "Filter by health status — needs_attention (cross-org reconciliation problems: unreconciled composition/master chains, split discrepancies) or low_completeness (below completeness score threshold: missing ISWC, credits, etc.). Both honour limit/offset. low_completeness reports `total` = works below the threshold by their stored score (max 200 per page). needs_attention checks at most 50 works per call and `offset` counts works CHECKED, not rows returned — a page can hold fewer rows than limit while more remain, so continue from next_offset until it is null; its `total` is set only when one call from offset 0 checks every candidate, otherwise it is null and the answer is the rows counted across pages.",
|
|
260
283
|
},
|
|
261
284
|
has_statement_data: {
|
|
262
285
|
type: "boolean",
|
|
@@ -304,7 +327,7 @@ export class WorksTools {
|
|
|
304
327
|
},
|
|
305
328
|
work_type: {
|
|
306
329
|
type: "string",
|
|
307
|
-
enum: [
|
|
330
|
+
enum: [...WORK_TYPE_VALUES],
|
|
308
331
|
description: "Filter by work type. 'instrumental' answers 'my instrumentals'.",
|
|
309
332
|
},
|
|
310
333
|
work_status: {
|
|
@@ -337,11 +360,11 @@ export class WorksTools {
|
|
|
337
360
|
},
|
|
338
361
|
primary_artist: {
|
|
339
362
|
type: "string",
|
|
340
|
-
description: "Exact primary-artist name match.",
|
|
363
|
+
description: "Exact primary-artist name match. Misses works whose primary_artist is empty — use `query` to include works linked by recording artist or credits.",
|
|
341
364
|
},
|
|
342
365
|
artist: {
|
|
343
366
|
type: "string",
|
|
344
|
-
description: "Exact primary-artist name match (alias).",
|
|
367
|
+
description: "Exact primary-artist name match (alias). Misses works whose primary_artist is empty — use `query` to include works linked by recording artist or credits.",
|
|
345
368
|
},
|
|
346
369
|
label: {
|
|
347
370
|
type: "string",
|
|
@@ -468,7 +491,7 @@ export class WorksTools {
|
|
|
468
491
|
"dossier",
|
|
469
492
|
],
|
|
470
493
|
},
|
|
471
|
-
description: 'Which data sections to include. Omit for all sections. Use ["basic"] for lightweight lookup. "licensing" surfaces the AI-disclosure / training-rights / provenance-attestation compliance fields (work_licensing satellite) in one scoped view. "dossier" assembles the full song dossier (recordings, releases, credits, people, provenance, AI disclosure, consent) — heavy, so it is opt-in only (never part of the default all-sections response); for the PDF, or several works in one ZIP, use pica_export_song_dossiers.',
|
|
494
|
+
description: 'Which data sections to include. Omit for all sections. Use ["basic"] for lightweight lookup. "health" is a short summary of THIS work\'s completeness (score, missing critical fields, suggestions) — opt-in, since "completeness" carries the full breakdown; for works across the catalogue needing attention use pica_works_query health_filter. "licensing" surfaces the AI-disclosure / training-rights / provenance-attestation compliance fields (work_licensing satellite) in one scoped view. "dossier" assembles the full song dossier (recordings, releases, credits, people, provenance, AI disclosure, consent) — heavy, so it is opt-in only (never part of the default all-sections response); for the PDF, or several works in one ZIP, use pica_export_song_dossiers.',
|
|
472
495
|
},
|
|
473
496
|
},
|
|
474
497
|
required: [],
|
|
@@ -515,7 +538,12 @@ export class WorksTools {
|
|
|
515
538
|
"mastering human-made, AI-assisted, or AI-generated? (Those four " +
|
|
516
539
|
"are set independently on ai_disclosure.) Always run " +
|
|
517
540
|
"pica_works_query first to check for a duplicate — two same-title " +
|
|
518
|
-
"works cause split and registration trouble.
|
|
541
|
+
"works cause split and registration trouble. If a refusal says the " +
|
|
542
|
+
"work already exists but it really is a different song with the same " +
|
|
543
|
+
"title and artist, retry with disambiguation (e.g. 'theme'). A remix is " +
|
|
544
|
+
"usually a new recording of the original song (pica_recordings_create, " +
|
|
545
|
+
"version_type 'remix'), not a new work; only a remix that added writers " +
|
|
546
|
+
"is its own work, linked with original_work_id. Album, artwork, " +
|
|
519
547
|
"track number, UPC, and Spotify URL live on the release " +
|
|
520
548
|
"(pica_releases_create/update), not here; label too, via the " +
|
|
521
549
|
"release (resolve the label name with pica_labels_query first). " +
|
|
@@ -570,6 +598,8 @@ export class WorksTools {
|
|
|
570
598
|
"MCP; set them through a release or recording path. " +
|
|
571
599
|
"Three jsonb compliance fields (ai_disclosure, provenance_attestation, training_rights) route " +
|
|
572
600
|
"to that same work_licensing satellite table and ARE set-able here. " +
|
|
601
|
+
"original_work_id links a modified version (a remix that added writers) to the work it modifies. " +
|
|
602
|
+
"Answers with a compact receipt — the id, each field you sent with the value now stored, and only the gaps this write opened; pass verbose: true for the full work. " +
|
|
573
603
|
"→ then: pica_works_inspect (verify changes persisted)",
|
|
574
604
|
workflows: ["work-required"],
|
|
575
605
|
vernacular_kind: "specialist",
|
|
@@ -582,6 +612,14 @@ export class WorksTools {
|
|
|
582
612
|
description: "Work ID (required)",
|
|
583
613
|
},
|
|
584
614
|
...WORK_WRITE_PROPERTIES,
|
|
615
|
+
...VERBOSE_PROPERTY,
|
|
616
|
+
append_notes: {
|
|
617
|
+
type: "string",
|
|
618
|
+
maxLength: 10000,
|
|
619
|
+
description: "Text to add to the end of the work's notes, on a new line, without reading or rewriting what is there. " +
|
|
620
|
+
"Safe when someone else edits the work at the same time (a conflict is retried, never overwritten). " +
|
|
621
|
+
"Cannot be sent together with notes.",
|
|
622
|
+
},
|
|
585
623
|
},
|
|
586
624
|
required: ["id"],
|
|
587
625
|
},
|
|
@@ -609,7 +647,7 @@ export class WorksTools {
|
|
|
609
647
|
description: "Delete a musical work. It is automatically unlinked from any " +
|
|
610
648
|
"releases it appears on (the release keeps its track). If the work " +
|
|
611
649
|
"still has dependent records (recordings, royalty payments, version " +
|
|
612
|
-
"links, claims) it is archived instead — hidden from the catalog but " +
|
|
650
|
+
"links, claims, signed split sheets) it is archived instead — hidden from the catalog but " +
|
|
613
651
|
"preserved — and the result tells you exactly what to remove to " +
|
|
614
652
|
"delete it permanently. A work with registry identifiers " +
|
|
615
653
|
"(ISRC/ISWC/MLC) is always archived, never destroyed. The result " +
|
|
@@ -637,8 +675,11 @@ export class WorksTools {
|
|
|
637
675
|
scope: "write:catalog",
|
|
638
676
|
audiences: ["stdio", "http", "public"],
|
|
639
677
|
tier: "write",
|
|
640
|
-
description: "
|
|
641
|
-
"
|
|
678
|
+
description: "Writes nothing: PICA keeps no single 'verified' flag on a work. Returns " +
|
|
679
|
+
"NO_WORK_VERIFIED_FLAG naming what the person can confirm themselves, signed in, " +
|
|
680
|
+
"on /inspect — the human-made stamp, their own writer credit, their own master " +
|
|
681
|
+
"share. Not a registration check (that's pica_works_verify_registrations). To " +
|
|
682
|
+
"review a work, use pica_works_inspect.",
|
|
642
683
|
workflows: "infrastructure",
|
|
643
684
|
vernacular_kind: "specialist",
|
|
644
685
|
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
|
|
@@ -669,7 +710,11 @@ export class WorksTools {
|
|
|
669
710
|
"how, and an optional signature hash (the raw signature itself is " +
|
|
670
711
|
"never stored). Sync libraries and provenance certificates use it " +
|
|
671
712
|
"to prove human authorship. To change or undo one, use the " +
|
|
672
|
-
"provenance revocation tools.
|
|
713
|
+
"provenance revocation tools. Only a signed-in person can make " +
|
|
714
|
+
"this statement: an API key, connected app or agent is refused " +
|
|
715
|
+
"with ATTESTATION_REQUIRES_HUMAN_SESSION — do not retry; tell the " +
|
|
716
|
+
"user to use 'stamp as human-made' at /inspect/works/<id>#human-made. " +
|
|
717
|
+
"After stamping, read it back with " +
|
|
673
718
|
"pica_works_inspect (sections: ['provenance']). → then: " +
|
|
674
719
|
"pica_works_inspect (verify it persisted), " +
|
|
675
720
|
"pica_provenance_certificate (generate a PDF)",
|
|
@@ -743,7 +788,7 @@ export class WorksTools {
|
|
|
743
788
|
"mode 'confirm_unregistered' records the user's own confirmation instead (no lookup; " +
|
|
744
789
|
"society_code optional — omitted means 'not registered with any society'). " +
|
|
745
790
|
"Idempotent — re-running refreshes checked_at. " +
|
|
746
|
-
"
|
|
791
|
+
"This is a registration check, not a human sign-off — a person's own statement that a human made the work is pica_works_attest (signed-in only).",
|
|
747
792
|
workflows: ["work-required"],
|
|
748
793
|
vernacular_kind: "specialist",
|
|
749
794
|
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
|
|
@@ -857,23 +902,80 @@ export class WorksTools {
|
|
|
857
902
|
* Query works — unified search, list, and health filter
|
|
858
903
|
*/
|
|
859
904
|
async queryWorks(args) {
|
|
905
|
+
// `verified` used to filter rows on `work.is_verified`, a column no
|
|
906
|
+
// applied migration adds to `works`, so it matched nothing whichever way
|
|
907
|
+
// it was set. Refuse
|
|
908
|
+
// it by name rather than ignore it: an agent that asked for "verified
|
|
909
|
+
// works" would otherwise read the whole catalogue as the answer.
|
|
910
|
+
if (args.verified !== undefined) {
|
|
911
|
+
return typedError("NO_WORK_VERIFIED_FLAG", "pica_works_query has no 'verified' filter: PICA does not keep a single 'verified' flag on a work. " +
|
|
912
|
+
"To find works missing what a registration needs, use health_filter: 'low_completeness' or " +
|
|
913
|
+
"completeness_score_lt; to review one work's confirmed and unconfirmed records, use pica_works_inspect.");
|
|
914
|
+
}
|
|
860
915
|
const limit = args.limit || 50;
|
|
861
916
|
const offset = args.offset || 0;
|
|
862
|
-
// Health filter routes to health/completeness APIs
|
|
917
|
+
// Health filter routes to health/completeness APIs.
|
|
918
|
+
//
|
|
919
|
+
// ⚠️ Both branches used to call the bare SDK method with no arguments,
|
|
920
|
+
// so `limit`/`offset` were ignored (the routes' own default of 20 won)
|
|
921
|
+
// and `total` was the length of that page — an agent asking for 100
|
|
922
|
+
// low-completeness works got 20 and was told there were 20 (ops issue
|
|
923
|
+
// 314325eb, 2026-09-25). Both now thread limit/offset through and report
|
|
924
|
+
// only the totals the server can actually vouch for.
|
|
863
925
|
if (args.health_filter === "needs_attention") {
|
|
864
|
-
|
|
865
|
-
|
|
926
|
+
// Each candidate is a full cross-org chain assembly, so the route caps
|
|
927
|
+
// a page at 50 candidates; `offset` pages candidates, not results.
|
|
928
|
+
const page = await this.pica.health.getWorksHealthPage({
|
|
929
|
+
limit: Math.min(limit, WORKS_HEALTH_MAX_PAGE),
|
|
930
|
+
offset,
|
|
931
|
+
unreconciledOnly: true,
|
|
932
|
+
});
|
|
933
|
+
const works = page.data;
|
|
934
|
+
const meta = page.meta;
|
|
935
|
+
// No meta (an older server) means nothing is known about the rest of
|
|
936
|
+
// the set: treat it as "more may remain" rather than "done".
|
|
937
|
+
const hasMore = meta ? meta.has_more : works.length > 0;
|
|
938
|
+
// A total is only claimed when THIS call, starting at offset 0, checked
|
|
939
|
+
// every candidate and the server said so (meta.has_more false).
|
|
940
|
+
// Otherwise the page length is not passed off as the total.
|
|
941
|
+
const wholeSetScanned = offset === 0 && meta !== undefined && !hasMore;
|
|
866
942
|
return formatStructuredList(works, "work", {
|
|
867
943
|
filter: "needs_attention",
|
|
868
|
-
total: works.length,
|
|
944
|
+
total: wholeSetScanned ? works.length : null,
|
|
945
|
+
...(wholeSetScanned
|
|
946
|
+
? {}
|
|
947
|
+
: {
|
|
948
|
+
total_note: hasMore
|
|
949
|
+
? "no total yet — the number needing attention is known only after every candidate work is checked; keep paging with next_offset and count the rows across pages"
|
|
950
|
+
: "this was the last page — the number needing attention is the rows counted across the pages you fetched",
|
|
951
|
+
}),
|
|
952
|
+
limit: meta?.limit ?? Math.min(limit, WORKS_HEALTH_MAX_PAGE),
|
|
953
|
+
offset,
|
|
954
|
+
scanned: meta?.scanned ?? null,
|
|
955
|
+
candidates_total: meta?.candidates_total ?? null,
|
|
956
|
+
hasMore,
|
|
957
|
+
next_offset: meta?.next_offset ?? null,
|
|
869
958
|
});
|
|
870
959
|
}
|
|
871
960
|
if (args.health_filter === "low_completeness") {
|
|
872
|
-
const
|
|
873
|
-
|
|
961
|
+
const page = await this.pica.health.getLowScoreWorksPage({
|
|
962
|
+
limit: Math.min(limit, LOW_COMPLETENESS_MAX_PAGE),
|
|
963
|
+
offset,
|
|
964
|
+
});
|
|
965
|
+
const works = page.data;
|
|
966
|
+
const meta = page.meta;
|
|
967
|
+
const appliedLimit = meta?.limit ?? Math.min(limit, LOW_COMPLETENESS_MAX_PAGE);
|
|
968
|
+
const total = meta?.total ?? null;
|
|
969
|
+
const hasMore = meta?.has_more ??
|
|
970
|
+
(total === null ? works.length >= appliedLimit : false);
|
|
874
971
|
return formatStructuredList(works, "work", {
|
|
875
972
|
filter: "low_completeness",
|
|
876
|
-
|
|
973
|
+
threshold: meta?.threshold,
|
|
974
|
+
total,
|
|
975
|
+
limit: appliedLimit,
|
|
976
|
+
offset,
|
|
977
|
+
hasMore,
|
|
978
|
+
next_offset: hasMore ? offset + appliedLimit : null,
|
|
877
979
|
});
|
|
878
980
|
}
|
|
879
981
|
// ADR-244 PR (c) §3.8 / §3.9 — queryable + sortable signals threaded
|
|
@@ -930,10 +1032,6 @@ export class WorksTools {
|
|
|
930
1032
|
let items = result.data || result;
|
|
931
1033
|
if (!Array.isArray(items))
|
|
932
1034
|
items = [];
|
|
933
|
-
// Client-side verified filter — preserved from pre-PR (c) behaviour.
|
|
934
|
-
if (args.verified !== undefined) {
|
|
935
|
-
items = items.filter((work) => work.is_verified === args.verified);
|
|
936
|
-
}
|
|
937
1035
|
// `light` projection — a full works row is ~1KB (completeness components,
|
|
938
1036
|
// streaming sub-scores, AI disclosure, provenance, album-art URLs), so a
|
|
939
1037
|
// 100-row page can blow the 100KB MCP response cap. When an agent only
|
|
@@ -1008,8 +1106,12 @@ export class WorksTools {
|
|
|
1008
1106
|
const allSections = !sections || sections.length === 0;
|
|
1009
1107
|
// Sections kept OFF the default `allSections` surface but still callable
|
|
1010
1108
|
// via explicit `sections`:
|
|
1011
|
-
// -
|
|
1012
|
-
//
|
|
1109
|
+
// - `health` is a summary of THIS work's completeness (the same
|
|
1110
|
+
// single-work read as `completeness`), so on the default surface it
|
|
1111
|
+
// would only repeat that section. Until 2026-09-26 it read the
|
|
1112
|
+
// ORG-WIDE works-health scan instead — ~25s, and about other works.
|
|
1113
|
+
// - `score` (ADR-201 closeout, 2026-04-27) is the org-wide PICA score;
|
|
1114
|
+
// it takes no workId.
|
|
1013
1115
|
// - `dossier` (2026-07-12) assembles the whole song (recordings,
|
|
1014
1116
|
// releases, credits, people, provenance, consent) — a heavy superset of
|
|
1015
1117
|
// the other sections, redundant on the default surface and worth its
|
|
@@ -1103,7 +1205,8 @@ export class WorksTools {
|
|
|
1103
1205
|
}
|
|
1104
1206
|
}
|
|
1105
1207
|
// ADR-201 closeout (2026-04-27): the section sub-calls used to run
|
|
1106
|
-
// sequentially. With ~8 calls each taking ~1s and one (
|
|
1208
|
+
// sequentially. With ~8 calls each taking ~1s and one (the org-wide
|
|
1209
|
+
// works-health scan `health` read until 2026-09-26)
|
|
1107
1210
|
// sometimes 25s+, the function would frequently exceed Vercel's 30s
|
|
1108
1211
|
// maxDuration on the default `allSections` path. Fan out everything
|
|
1109
1212
|
// in parallel — each branch still owns its own try/catch fallback so
|
|
@@ -1145,11 +1248,22 @@ export class WorksTools {
|
|
|
1145
1248
|
})());
|
|
1146
1249
|
}
|
|
1147
1250
|
}
|
|
1148
|
-
// Quality sections — fan out in parallel.
|
|
1251
|
+
// Quality sections — fan out in parallel. `health` and `completeness`
|
|
1252
|
+
// share ONE read of this work's completeness route.
|
|
1253
|
+
let completenessRead = null;
|
|
1254
|
+
const readCompleteness = () => (completenessRead ??= this.pica.health.getWorkCompleteness(workId));
|
|
1149
1255
|
if (has("health")) {
|
|
1150
1256
|
sectionTasks.push((async () => {
|
|
1151
1257
|
try {
|
|
1152
|
-
|
|
1258
|
+
const c = await readCompleteness();
|
|
1259
|
+
result.health = {
|
|
1260
|
+
scope: "work",
|
|
1261
|
+
work_id: workId,
|
|
1262
|
+
score: c?.score ?? null,
|
|
1263
|
+
missing_field_count: c?.missing_field_count ?? null,
|
|
1264
|
+
missing_critical: c?.missingCritical ?? [],
|
|
1265
|
+
suggestions: c?.suggestions ?? [],
|
|
1266
|
+
};
|
|
1153
1267
|
}
|
|
1154
1268
|
catch (e) {
|
|
1155
1269
|
result.health = null;
|
|
@@ -1160,9 +1274,34 @@ export class WorksTools {
|
|
|
1160
1274
|
if (has("completeness")) {
|
|
1161
1275
|
sectionTasks.push((async () => {
|
|
1162
1276
|
try {
|
|
1163
|
-
const completeness = await
|
|
1277
|
+
const completeness = await readCompleteness();
|
|
1164
1278
|
result.completeness = completeness;
|
|
1165
1279
|
hints.push(...mapGapsToHints(completeness?.gaps || completeness?.missing || []));
|
|
1280
|
+
// ops_issue 1bc63a6d — only an instrumental/PA is attached. The
|
|
1281
|
+
// candidates (unassigned files whose name matches the title) sit
|
|
1282
|
+
// in completeness.audio.gap; they are for the user to confirm.
|
|
1283
|
+
if (completeness?.audio?.checked === false) {
|
|
1284
|
+
hints.push({
|
|
1285
|
+
gap: "audio_check_unavailable",
|
|
1286
|
+
suggestion: "PICA could not check this work's attached audio just now, so an 'only an instrumental is attached' gap may be missing from this answer.",
|
|
1287
|
+
severity: "nice_to_have",
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
const vocalGap = completeness?.audio?.gap;
|
|
1291
|
+
if (vocalGap && typeof vocalGap.label === "string") {
|
|
1292
|
+
const ids = Array.isArray(vocalGap.candidates)
|
|
1293
|
+
? vocalGap.candidates
|
|
1294
|
+
.map((c) => c.audio_file_id)
|
|
1295
|
+
.filter(Boolean)
|
|
1296
|
+
: [];
|
|
1297
|
+
hints.push({
|
|
1298
|
+
gap: "only_instrumental_attached",
|
|
1299
|
+
suggestion: ids.length > 0
|
|
1300
|
+
? `${vocalGap.label} (${ids.join(", ")}). If the user confirms one is the vocal master, it can be attached with pica_audio_assign.`
|
|
1301
|
+
: `${vocalGap.label}.`,
|
|
1302
|
+
severity: "important",
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1166
1305
|
}
|
|
1167
1306
|
catch (e) {
|
|
1168
1307
|
result.completeness = null;
|
|
@@ -1387,7 +1526,7 @@ export class WorksTools {
|
|
|
1387
1526
|
artistId: args.primary_artist_id ?? null,
|
|
1388
1527
|
}, existing);
|
|
1389
1528
|
if (match)
|
|
1390
|
-
versionWarning = buildVersionWarning(match);
|
|
1529
|
+
versionWarning = buildVersionWarning(match, String(work.id));
|
|
1391
1530
|
}
|
|
1392
1531
|
}
|
|
1393
1532
|
catch {
|
|
@@ -1426,13 +1565,33 @@ export class WorksTools {
|
|
|
1426
1565
|
* Update work
|
|
1427
1566
|
*/
|
|
1428
1567
|
async updateWork(args) {
|
|
1429
|
-
const {
|
|
1568
|
+
const { verbose, rest } = takeVerbose(args);
|
|
1569
|
+
const { id, ...updates } = rest;
|
|
1570
|
+
// Compact receipt (ops 61ce1dcc): read the gaps open BEFORE the write so
|
|
1571
|
+
// the answer can name only the ones this write opened. Skipped when the
|
|
1572
|
+
// caller asked for the full record, which repeats every gap anyway.
|
|
1573
|
+
const before = verbose
|
|
1574
|
+
? null
|
|
1575
|
+
: await buildIntegrityPressure(this.pica, "work", id);
|
|
1430
1576
|
const work = await this.pica.works.update(id, updates);
|
|
1431
1577
|
// Registration integrity floor (Task 9) — same pressure rendering as
|
|
1432
1578
|
// createWork, so an update that clears (or introduces) a gap keeps the
|
|
1433
1579
|
// agent informed without a separate inspect round-trip.
|
|
1434
1580
|
const pressure = await buildIntegrityPressure(this.pica, "work", work.id);
|
|
1435
|
-
|
|
1581
|
+
if (verbose) {
|
|
1582
|
+
return formatSuccess("Work updated successfully", { ...work, integrity: pressure.integrity }, pressure.hints);
|
|
1583
|
+
}
|
|
1584
|
+
const openBefore = before?.integrity
|
|
1585
|
+
? new Set(before.integrity.gaps.map((g) => g.code))
|
|
1586
|
+
: null;
|
|
1587
|
+
const { changed, unconfirmed } = changedFields(updates, work);
|
|
1588
|
+
return formatSuccess("Work updated successfully", {
|
|
1589
|
+
id: work?.id ?? id,
|
|
1590
|
+
changed,
|
|
1591
|
+
...(unconfirmed.length > 0 ? { unconfirmed } : {}),
|
|
1592
|
+
integrity: slimIntegrity(pressure.integrity, openBefore),
|
|
1593
|
+
receipt: RECEIPT_NOTE,
|
|
1594
|
+
}, newHintsOnly(before?.integrity ? before.hints : null, pressure.hints));
|
|
1436
1595
|
}
|
|
1437
1596
|
/**
|
|
1438
1597
|
* Delete work
|
|
@@ -1442,44 +1601,28 @@ export class WorksTools {
|
|
|
1442
1601
|
return formatDeletion("work", args.id, outcome);
|
|
1443
1602
|
}
|
|
1444
1603
|
/**
|
|
1445
|
-
*
|
|
1604
|
+
* pica_works_verify — writes nothing, and says so.
|
|
1605
|
+
*
|
|
1606
|
+
* It used to PATCH `is_verified: true` onto the work. `works` has no such
|
|
1607
|
+
* column (no applied migration adds one), so the route's allowed-field check
|
|
1608
|
+
* refused it on every call — its only two calls ever both failed (DonnySavv,
|
|
1609
|
+
* feedback 86e26f75, 2026-09-28). PICA keeps no work-level "metadata
|
|
1610
|
+
* verified" flag, and inventing one would record an agreement nobody made.
|
|
1611
|
+
* What a person CAN confirm lives on specific records, each refused to
|
|
1612
|
+
* agents and each done by the person on /inspect — this names them.
|
|
1446
1613
|
*/
|
|
1447
1614
|
async verifyWork(args) {
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
try {
|
|
1452
|
-
const baseUrl = this.pica.works["baseUrl"];
|
|
1453
|
-
const apiKey = this.pica.works["apiKey"];
|
|
1454
|
-
const res = await fetch(`${baseUrl}/admin/export-readiness/${args.id}`, {
|
|
1455
|
-
headers: {
|
|
1456
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1457
|
-
"Content-Type": "application/json",
|
|
1458
|
-
},
|
|
1459
|
-
});
|
|
1460
|
-
if (res.ok) {
|
|
1461
|
-
const json = (await res.json());
|
|
1462
|
-
exportReadiness = json.data ?? json;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
catch {
|
|
1466
|
-
// Readiness check is best-effort — don't fail verification
|
|
1467
|
-
}
|
|
1468
|
-
const summaryParts = ["Work verified successfully"];
|
|
1469
|
-
if (exportReadiness) {
|
|
1470
|
-
summaryParts.push(`Export readiness: ${exportReadiness.ready ? "ready" : "not ready"}`, `Evidence: ${exportReadiness.evidenceStrength}`, `Attestation: ${exportReadiness.attestationCoverage}%`);
|
|
1471
|
-
if (Array.isArray(exportReadiness.blockers) &&
|
|
1472
|
-
exportReadiness.blockers.length > 0) {
|
|
1473
|
-
summaryParts.push(`Blockers (${exportReadiness.blockers.length}):`);
|
|
1474
|
-
for (const b of exportReadiness.blockers) {
|
|
1475
|
-
summaryParts.push(` ${b.type}: ${b.resolutionHint}`);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1615
|
+
const workId = typeof args.id === "string" ? args.id.trim() : "";
|
|
1616
|
+
if (!workId) {
|
|
1617
|
+
return typedError("VALIDATION_ERROR", "id is required.");
|
|
1478
1618
|
}
|
|
1479
|
-
return
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1619
|
+
return typedError("NO_WORK_VERIFIED_FLAG", "PICA does not keep a single 'verified' flag on a work, so nothing was written. " +
|
|
1620
|
+
"What a person can confirm, each while signed in to PICA (an assistant cannot do these for them): " +
|
|
1621
|
+
`(1) the human-made stamp — "stamp as human-made" at /inspect/works/${workId}#human-made; ` +
|
|
1622
|
+
`(2) their own writer credit — confirm it in the credits table on /inspect/works/${workId}; ` +
|
|
1623
|
+
'(3) their own master ownership share — "confirm my share" under ownership splits at ' +
|
|
1624
|
+
"/inspect/recordings/<recording_id>#ownership-splits. " +
|
|
1625
|
+
"To review what is missing or unconfirmed on the work, use pica_works_inspect.");
|
|
1483
1626
|
}
|
|
1484
1627
|
/**
|
|
1485
1628
|
* Bulk delete works
|