Jamaican Music Historical Catalogue
Status: Seed v1.0 (locked reference, 2026-09-22) — not a complete census. Public hub:
/catalogue· Documents on-site:/catalogue/docs/· Stats API:GET /api/catalogue/stats· Seed manifest:src/data/catalogue/manifest.jsonv1.0 is the version of the reference documents and the seed data they lock (SHA-256 checksums in the manifest). The catalogue’s coverage is still a seed plus one MusicBrainz relationship hop — see the coverage table on the hub and seed §30.
A research / historical-census layer for JamaicansMusic.com. Its job is to answer “how many songs, albums, riddims and artists has Jamaica produced?” in a way that can defend every number: what was counted, what was excluded, which duplicates were merged, and which source supports each fact.
The two source-of-truth documents are checked in alongside this file:
| Document | Purpose |
|---|---|
MASTER-CATALOGUE-SEED.md — v1.0 | Full data model, evidence vocabulary, taxonomy, seed registries (hand-curated §7/§13 plus the generated MusicBrainz neighbourhood §7.1/§13.1), dedup keys, MusicBrainz plan with phase status, coverage statistics (§23.1), revision history and the seed manifest. Authoritative for schema and rules. |
HISTORICAL-ESTIMATES.md — v1.0 | Per-genre estimate tables and de-duplicated grand totals with methodology, plus how the estimates relate to the seeded rows. Authoritative for the working estimates only; never database counts. |
vybz-kartel-complete-discography-v1.md | Per-artist research deliverable (v1, locked): ~1.7k Confirmed/Probable unique Vybz Kartel recordings plus an Unverified section, sourced from MusicBrainz/Discogs et al. Not a completeness claim; not yet imported into hist_* or Listen. |
vybz-kartel-complete-discography-v1.1.md | v1.1 riddim-name fill over locked v1: +365 named riddims (110 → 475), 281 Riddim-category rows still Unidentified; sources Discogs 7” notes + Riddim-ID/Riddimguide. Still not a completeness claim; not imported into hist_*. See vybz-kartel-discography-v1.1-CHANGELOG.md. |
vybz-kartel-listen-batches.md | How much of the census the Listen library plays: Batches 1–3 counts (610 → 784 songs), per-album fills, the honest remainder (album holes and Confirmed singles with no official upload), and the stop / Batch 4 recommendation. |
1. Purpose and non-goals
Goal. Build the closest possible historical census of Jamaican genres, works, recordings, release groups, releases, riddims, artists, labels, studios and their relationships — supporting both historical research (evidence, citations, uncertainty, alternate names) and computational counting (deduplicated totals by genre, decade, origin, riddim, label, …).
Non-goals for Seed v1.0.
- It is not a claim to contain every Jamaican song, artist, album or riddim.
- It does not publish a single “total”. Large figures are estimates, labelled as such.
- It does not assert any artist’s birthplace, nationality or heritage (see §4).
2. Relationship to the Listen catalog
The site already has a Listen music library (music_artists, canonical_songs,
recording_videos, riddims, labels, album_projects, …) built around what can be
played from YouTube today. The Historical Catalogue is a different thing: what history
records, whether or not a playable video exists.
They are deliberately not joined in v1:
| Listen catalog | Historical Catalogue | |
|---|---|---|
| Tables | unprefixed (canonical_songs, riddims, …) | all prefixed hist_ |
| Drizzle module | src/db/schema.ts | src/db/schema-catalogue.ts (re-exported from schema.ts) |
| Unit of “song” | a canonical song with ≥1 YouTube recording | work ≠ recording ≠ track ≠ release group ≠ release |
| Truth | curated + import pipelines, publish status | evidence status + source assertions |
| Public read path | exported JSON (src/data/music/) | seed JSON (hand registries + generated MusicBrainz neighbourhood) now; hist_* tables via API later |
How they may be bridged later (not in v1): a nullable hist_recording_id on
canonical_songs and/or a hist_recording_links table (hist_recording_id, canonical_song_id, link_status, source_id). Any bridge is a link, never a merge — a Listen song may map to
several historical recordings (versions), and most historical recordings will have no
playable video. The riddims table in Listen (“Top Riddims”) and hist_riddims are
likewise separate until a reviewed alias graph exists.
3. Non-negotiable counting rules (summary of seed §2, §20)
- A song is not a recording. Work (composition) → recording (performance/mix) →
track (recording on a release) → release group (album concept) → release (pressing).
The headline album count is
count(distinct hist_release_groups.id where primary_type = 'album'); reissues/editions live inhist_releasesand never inflate it. - Riddims are first-class. One riddim, many recordings.
known_recording_countonhist_riddimsis a snapshot with a source and access date, never a lifetime total. - Artists are deduplicated by evidence, not by name: same MBID, documented legal identity + alias relationship, or same group identity.
- Genre membership is many-to-many. Totals are deduplicated by recording id (or use a single primary genre); genre columns are never summed.
- Explicit date precision —
YYYY,YYYY-MMorYYYY-MM-DDas text, never a guessed day. - Preserve printed spellings (
title_as_printed) separately from normalised titles. - Never overwrite a disputed value silently —
hist_change_logandhist_source_assertionskeep the prior claim.
4. Evidence vocabulary and origin policy
Every historically important field carries one of (hist_evidence_status enum):
| Status | Meaning |
|---|---|
VERIFIED_PRIMARY | Official archive, institutional catalogue, original release artifact, rights/label documentation |
VERIFIED_SECONDARY | Reliable scholarly / reference source |
DATABASE_DERIVED | Present in a specialist music database, not independently confirmed |
CANDIDATE | Plausible, awaiting source verification |
CONFLICTED | Reliable sources disagree |
UNKNOWN | No adequate evidence located |
ORAL_HISTORY | Interview / oral testimony, retains attribution |
Field-level confidence is high | medium | low | unknown (hist_confidence).
Artist origin (hist_artists.origin_bucket, enum hist_origin_bucket) is one of
jamaican_born, jamaican_descent_born_abroad, non_jamaican_born_jamaican_genre_artist,
mixed_or_multinational_group, unknown. The default — and the value for every seeded
artist, including the 84 imported from MusicBrainz — is unknown. It may only change when a hist_source_assertions row documents
birthplace / parentage. tests/catalogue.test.js fails if the seed asserts anything else.
5. Schema (migration 0011_historical_catalogue.sql)
24 tables, all hist_-prefixed. Mirrors seed §16 as a focused v1.
| Area | Tables | Notes |
|---|---|---|
| Provenance | hist_sources, hist_source_assertions, hist_research_notes, hist_change_log | Assertions = “field X on entity Y has value Z because source Q says so”. Notes carry a review queue (identity / chronology / riddim_lineage / origin_evidence / general). |
| Genres | hist_genres (parent_genre_id, section, inclusion_status, is_style_tag), hist_genre_relations | Calypso/Soca are stored with EXCLUDE so they cannot contaminate totals. |
| Artists | hist_artists, hist_artist_aliases, hist_artist_genres, hist_artist_memberships | MBID / Wikidata / Discogs id columns; origin_bucket default unknown. Since v1.0 memberships (MusicBrainz member of band) and sourced aliases are populated. |
| Riddims | hist_riddims (parent_riddim_id, source_recording_id, source_work_id, snapshot count fields), hist_riddim_aliases, hist_riddim_relations (seed §21 vocabulary), hist_riddim_genres | |
| Works / recordings | hist_works, hist_recordings (version_type, riddim_id, work_id), hist_recording_artists, hist_recording_genres | |
| Releases | hist_release_groups (primary_type, secondary_types), hist_release_group_artists, hist_release_group_genres, hist_releases | |
| Contributors | hist_labels, hist_studios | minimal in v1 |
Deferred to v0.2: hist_release_tracks, hist_recording_credits (controlled roles from
seed §15), hist_work_writers, hist_artist_origins history table.
The migration is idempotent and runs on Netlify DB / Neon / local Postgres / PGlite.
Apply manually with npm run db:migrate:sql -- 0011_historical_catalogue if not on Netlify.
6. Seed data
Checked-in JSON under src/data/catalogue/ (typed by src/lib/catalogue/seed.ts). Two kinds of file:
hand-curated registries (edited by historians, mirrored in the seed document) and generated files
(written by scripts, never hand-edited, re-runnable). Counts are Seed v1.0 (2026-09-22).
| File | Kind | Seed section | Rows |
|---|---|---|---|
genres.json | hand | §5.1–§5.3 taxonomy | 37 (33 included + 4 related-Caribbean exclusions) |
artists.json | hand | §7 seed artist registry | 53 — all origin_bucket = unknown (46 from v0.1 + 7 credited on seed recordings) |
artist-mbids.json | generated (catalogue:mb-phase-a -- --resolve-seed-mbids) | §7 sidecar | 49 accepted MBIDs, 4 for review. Identity only, never origin. |
riddims.json | hand | §9 registry + JIS lineage candidates | 10 with Riddim Guide snapshot counts + 3 CANDIDATE rows |
recordings.json | hand | §11 heritage / Sleng Teng / Real Rock / Stalag seeds | 34 (23 linked to registry artists) |
release-groups.json | hand | §13 album registry | 9 — every row has a MusicBrainz release-group MBID and first-release date since v1.0 |
labels.json | hand | §14 label registry | 10 |
sources.json | hand | §18 priority source registry | 17 (SRC001–SRC017; SRC016 = MusicBrainz WS/2 snapshot, SRC017 = Kartel discography v1.1) |
estimates.json | hand | HISTORICAL-ESTIMATES tables | kind: "ESTIMATES" — never a DB count |
musicbrainz/seed-neighbourhood.json | generated (catalogue:mb-phase-b -- --neighbourhood) | §7.1 / §13.1 | 84 artists, 104 memberships, 240 aliases, 2,874 release groups — one MusicBrainz hop from the seed, all DATABASE_DERIVED citing SRC016 |
manifest.json | generated (catalogue:manifest) | seed manifest | SHA-256 + row count per file, seed counts; mirrored in the seed document |
Totals after seeding: 137 artists (53 + 84), 240 aliases, 104 memberships, 13 riddims, 34 recordings, 2,883 release groups (2,127 albums), 10 labels, 37 genres, 17 sources, ≈ 6,000 source assertions.
npm run catalogue:seed upserts everything into hist_* (keyed by seed id, safe to re-run, chunked),
writes a hist_source_assertions row for each seeded fact (title / MBID / first-release date per release
group, one per accepted musicbrainz_artist_id citing SRC008, …), rebuilds the seed-script memberships,
and files an origin_evidence research note for every artist plus riddim_lineage notes for the JIS candidates.
MusicBrainz guardrails (Phase A + B): bulk MusicBrainz data lives only in the scratch database
named by CATALOGUE_SCRATCH_DATABASE_URL; the Phase B neighbourhood import uses the public WS/2 API at
≤ 1 request / 1.5 s with an identifying User-Agent and requests no tags (CC BY-NC-SA); origin_bucket
is never set from a MusicBrainz area; MusicBrainz tags are never imported as hist_genres; no MBID is
invented (ambiguous seed matches go to review); generated seed ids (A100001…, AL100001…) are stable
across re-runs.
validateCatalogueSeed() enforces: unique ids/slugs/MBIDs, controlled vocabularies, resolvable
references, date precision, origin_bucket = unknown, and that every generated row is DATABASE_DERIVED
with an MBID and a registered snapshot source.
Seed manifest (v1.0 lock). npm run catalogue:manifest writes manifest.json and the
<!-- seed-manifest --> block in MASTER-CATALOGUE-SEED.md; npm run catalogue:manifest -- --check and
npm test fail when a seed file changed without regenerating both. Editing seed data therefore always
means: edit JSON → npm test → npm run catalogue:manifest → commit all three.
7. Public surface
/catalogue(src/pages/catalogue.astro, static) — states it is a seed, carries a “Data as of” stamp (the manifest’sgeneratedAt, i.e. when the seed data last changed, plus the seed version), shows the four estimate cards (visibly tagged ESTIMATE), a Listen library box (songs / albums / riddims fromsrc/data/music/meta.jsonand artist profiles from the content collection — the same sources/listenand/artistsuse, shown as a separate layer), the seeded-so-far strip (hand-curated vs MusicBrainz neighbourhood split), the coverage statistics table, counting rules, evidence legend, estimates-by-genre table, genre taxonomy, seed riddims (+ lineage candidates), the hand-curated artist registry (names link to/artists/<slug>where a profile exists — resolved throughsrc/lib/music/artistSlug.tsso no._slug can leak — with MusicBrainz as a secondary link), the MusicBrainz neighbourhood summary (memberships, release groups by type), sources, the phased plan with live status and links to these documents./catalogue/docs/<slug>/(src/pages/catalogue/docs/[slug].astro, static) — every markdown file in this folder rendered as a site page at build time (content collectioncatalogueDocs, registry insrc/lib/catalogue/docs.js), with canonical URLs, meta descriptions, a contents list and sitemap entries;/catalogue/docs/is the index. The repository is private, so GitHub links are never shown to visitors; in-document links between these files are rewritten to the site pages (src/lib/catalogue/rehypeCatalogueDocLinks.js). The locked Kartel v1 document is servednoindexas superseded.- Planned sub-pages that were never built (
/catalogue/artists,/albums,/riddims,/labels,/genres,/eras) 301 to the closest existing page (netlify.toml). GET /api/catalogue/stats(netlify/functions/catalogue-stats.mts→src/lib/catalogue/handleCatalogueStatsRequest.ts) —{ seedVersion, seeded: { source, counts }, coverage[], musicbrainzNeighbourhood, estimates, docs }.seeded.sourceisdatabasewhen thehist_*tables answer,seed_filesotherwise; both report the same keys (artists,artistsHandCurated,artistsFromMusicbrainz,artistAliases,artistMemberships,releaseGroups,releaseGroupsAlbums, …).docsholds the on-site document URLs.
7.1 Automation — monthly seed refresh
.github/workflows/catalogue-seed-refresh.yml runs on the 2nd of every month (and on
workflow_dispatch, with a dry_run input). It re-runs the Phase B neighbourhood import against
the public MusicBrainz WS/2 API and opens a pull request only when the data changed:
npm run catalogue:mb-phase-b -- --neighbourhood— rate-limited to one request per 1.5 s with the identifyingUser-Agentfromsrc/lib/catalogue/musicbrainz/dump.ts, 503/429 back-off.npm run catalogue:neighbourhood-diff -- --restore --sync-source— compares the new file with the committed one ignoring the run stamps (accessedDate,resolvedAt,userAgent). No material change → the file is restored and the job ends. Otherwise SRC016’saccessedDateinsources.jsonis moved to the new snapshot date and a before/after summary is written.npm run catalogue:manifest→npm run catalogue:validate→npm run catalogue:manifest -- --check.peter-evans/create-pull-requestopens (or updates) a PR fromautomation/catalogue-seed-refreshwith the summary and a review checklist. Only the defaultGITHUB_TOKENis used; nothing is pushed tomain; the site database, Listen catalog and AdSense/GA configuration are untouched.
The exact-count locks in tests/catalogue.test.js and the figures quoted in this README are meant
to move with such a PR — the reviewer updates them (and decides on a document version bump) before
merging. The hub’s “Data as of” stamp follows the manifest date automatically.
8. Phased plan
| Phase | Goal | Where |
|---|---|---|
| A — MusicBrainz backbone | Ingest artists, aliases, works, recordings, release groups, releases, labels, areas, relationships from the CC0 dump into a scratch Postgres; retain MBIDs permanently. | Implemented (scratch + extracts): npm run catalogue:mb-phase-a -- --check / --download / --unpack / --load / --extract / --all resolves LATEST, verifies + unpacks the dump, COPYs 39 tables into CATALOGUE_SCRATCH_DATABASE_URL (never DATABASE_URL), writes the Phase B candidate NDJSON extracts to .cache/catalogue/extracts/<dump-id>/ and a citable hist_sources snapshot record (SRC-MB-<dump-id>). Seed artists carry MBIDs via src/data/catalogue/artist-mbids.json (49 accepted, 4 for review). See src/scripts/catalogue/README.md. |
| B — Jamaican candidate set | Combine Jamaican artist areas, JM release countries, genre tags, known labels/producers, riddim-linked artists and curated historian lists. No single signal is proof. | Neighbourhood import implemented (v1.0): npm run catalogue:mb-phase-b -- --neighbourhood walks one member of band hop and every credited release group from each MBID-resolved seed artist via the WS/2 API into src/data/catalogue/musicbrainz/seed-neighbourhood.json (84 artists, 104 memberships, 240 aliases, 2,874 release groups), imported by catalogue:seed as DATABASE_DERIVED rows citing SRC016. Scoring of the Phase A extracts (artists_jamaica_area, releases_jm, labels_jm, recordings_on_jm_releases, seed_neighbourhood) into a reviewed bulk candidate list is not started. |
| C — Specialist reconciliation | Map Riddim Guide artists/tunes/riddims, Riddims World riddims, UWI Roots Knotty Roots singles and the Kartel discography (SRC017) onto internal entities; historian corrections land in hist_source_assertions. | Not started. |
Seed §30 separates the document/seed version (v1.0, locked) from catalogue coverage
milestones: next is coverage 0.2 (score the Phase A extracts, label MBIDs, recording-level
reconciliation for the §11 seed and SRC017, origin evidence for the first 50 hand-curated artists,
riddim alias graph, duplicate reports); the catalogue 1.0 target (historian dashboard, alias
workflow, CSV export) is unchanged.
9. What historians can do next
Work queues are already modelled (hist_research_notes.queue):
- Origin evidence — 137 open notes, one per seeded artist (53 hand-curated first). Attach a
source assertion for
birth_place/birth_country/jamaican_parentage_note, then setorigin_bucket. - Riddim lineage — verify Answer ↔ “Never Let Go”, Real Rock’s Studio One origin, and the
My Conversation / Queen Majesty / Lonely Street candidates; set
source_recording_id,first_known_yearandhist_riddim_relations(same_as,remake_of, …). - Identity — Burning Spear person/group (MusicBrainz resolves the seed MBID to a Person; the
§7 row stays
unknownuntil reviewed); Charlie Chaplin (A000050, deejay vs actor — MBID is the disambiguated reggae entry); disambiguateGeneral [1]andVolcano [2]; pick or reject the fourreviewMBIDs (Lynn Taitt, Lord Fly, Harold Richardson, Sugar Belly Walker); review the 84 neighbourhood artists (all one documented hop from a seed anchor, none hand-verified). - Chronology — the nine §13 albums now have MBIDs and first-release dates; review the 195 undated and the 704 compilation release groups in the neighbourhood; decide which MusicBrainz “Album” rows are really reissue compilations.
- Works — link the 34 seed recordings to
hist_works; decide which[Dub …]/Versionentries share a work; resolve the six Riddim GuideDub …credits on Stalag to artists.
Add rows by editing the JSON in src/data/catalogue/ (validated by npm test), re-running
npm run catalogue:manifest and npm run catalogue:seed; larger imports go through the Phase A/B/C scripts.