Skip to content

28 — Recordings, Variants & Releases (envisioning)

▶ OPTION C BACKEND BUILT 2026-06-28. Track gained variantType (original · radioEdit · explicit · clean · instrumental · remix · live), relations: [{track, type}] (alternateOf · remixOf · sameRecordingAs, mirror-written both ways) and appearsInProjects[] (secondary release membership). Endpoints: GET/POST/DELETE /track/:id/relations · POST/DELETE /track/:id/appearances · variantType via PUT /track/:id. nest build + jest + e2e green. Remaining = iOS (variant chips

  • group-by/cross-link lenses, doc-26) and Option B (formal Release/UPC) if distribution is later formalized. Also unblocks doc-46 per-variant (appliesToVariants). See CHANGELOG.md.

Envisioning doc — needs confirmation. Governed by 00-ROADMAP.md. Created 2026-06-10. Explores how Sonnance should model the music-industry reality the owner flagged: remixes, radio edits, explicit/clean versions, and the same track appearing on multiple albums/compilations — and how to navigate between them (search · group-by · cross-reference hyperlinks). Composes with the "one graph, many lenses" backbone from 26-IDENTITY-SHARING-NAVIGATION.md. Nothing built yet — decisions to confirm at the end.

The problem (what the owner spotted)

Real catalogs aren't a clean tree. One "song" spawns:

  • Variants / edits — radio edit, explicit, clean, instrumental, remix, live. Distinct deliverables (usually distinct ISRCs), not revisions of a work-in-progress.
  • Multiple appearances — the same recording shows up on the original single, the album, and a compilation / "best of". A many-to-many between recording and release.

Users then need to get between these: search, group-by, and cross-reference links that hop to "the explicit version", "the remix", "the album this also appears on".

Why today's model can't express it (verified against the schema)

Sonnance is ArtistSpace → Project → Track → File(versions):

  • Track conflates "the song" with "a specific recording." Its File[] "versions" are production revisions (v1/v2/v3 mixes — the review loop), not variants. A radio edit is not "v4 of the mix."
  • Track.project is single-valued (required), so a track cannot belong to two releases — multi-album / compilation membership is unrepresentable.
  • Useful raw material already exists: label (string), ISRC (per-recording id), UPC (per-release id), genre, releaseDate on Track. So the fields already hint at the recording/release split.

The industry frame (for shared vocabulary)

Standard model (MusicBrainz / DDEX-flavored):

  • Work — the composition (the song as written).
  • Recording — a specific recorded performance; carries an ISRC. Radio edit / explicit / clean / remix / live = different recordings of the same work.
  • Release — a published package (single / album / compilation); carries a UPC.
  • Track-on-release — a recording's appearance on a release at a position. This is the many-to-many join.

Sonnance's Track today ≈ a Recording; Project ≈ a Release-ish container; File[] = production revisions (a Sonnance-specific, valuable concept the standard model lacks).

Three options

A — Labels-only (lightest). Add a variantType tag + lean on label; express relationships only as free text. Cheap, but "appears on multiple albums" stays unrepresentable and cross-links are manual. Too weak.

B — Full Recording/Release remodel (most correct). Introduce real Release (UPC, many-to-many to recordings) and a Work grouping; rename ProjectRelease. Models reality precisely, scales to distribution/DDEX — but a large remodel + migration, over-built for this stage.

C — Pragmatic middle (recommended). Keep the hierarchy; add two small things to Track and lean on doc-26 lenses for navigation:

  1. variantType enum on Track — original | radioEdit | explicit | clean | instrumental | remix | live (+ existing label for finer free-text). Per-variant ISRC already supported.

  2. Bidirectional cross-referencesrelations: [{ track, type }] where type ∈ { alternateOf, remixOf, sameRecordingAs }, and appearsInProjects: [projectId] as a secondary membership (the canonical project stays the home; this adds extra appearances). Writes mirror both directions so the hyperlink works back and forward.

    Navigation falls out of doc 26's "one graph, many lenses": variantType + label + relations become tags + lenses + cross-link chipssearch, group-by (variant / label / release), and tappable "Explicit" / "Radio Edit" / "Also on: Compilation X →" chips that hop between related items. No new navigation engine — it reuses the lens model.

Recommendation: C. It serves every case the owner named (remix, radio edit, explicit, multi-album, cross-reference) with modest backend (one enum + two array fields on Track + mirrored-write logic), keeps the review loop intact, and leaves a clean path to B later if distribution/UPC management is formalized.

Backend map (reuse vs net-new, for Option C)

CapabilityTodayNet-new
Production versions (v1/v2/v3)File[]none — unchanged
Variant type (radio/explicit/remix…)variantType enum on Track (+ reuse label)
Per-variant ISRCISRC on Tracknone
Cross-reference between tracksrelations: [{ track, type }] + mirrored writes; a resolve/expand on read
Appears on multiple releases❌ (project single)secondary appearsInProjects: [projectId] (canonical project stays home)
Search / group-by / filter🟡 search module existssurface variant/label/relation as lenses (doc 26)
Formal Release (UPC, true M2M)only if Option B is later adopted

How this sequences

This is a new big capability, like sharing (doc 26) — envision now, build later. Order on the roadmap: polish/stabilize (current) → real-user feedback → then the deferred systems (sharing doc 26, variants doc 28), sequenced by what feedback shows users actually reach for. One cheap piece could ride along earlier: the variantType tag is trivial and would pair naturally with the cover/metadata polish if you want an early taste — flagged as optional, not now.

Cover art & releases (envisioning addendum, 2026-06-12)

Owner's questions from the cover slice: do covers have versions? which version is this cover? does the Portal hold only the single's art, or album/compilation art too?

  • Covers already have versions. Track.image[] is a history — every upload pushes a new image and flips imageFinal (exact mirror of audio[]/audioFinal). "Which version is this?" = imageFinal. The Portal currently shows only the final; a cover history (browse image[], set an older one as cover) becomes possible the moment the backend set-cover-from-existing verb lands (✅ setCoverImage shipped 2026-06-12 — promote; the delete-from-history half is still queued). Per-cover approval already has its slot — the releaseStatus.cover dimension — and could later adopt the doc-29 per-file pattern if cover review needs the same rigor as audio. The cover-approval home and this whole dimension are now grounded in 32-APPROVAL-MODEL.md (owner: "cover approval belongs in the Single/Project/CD area, not the Wave").
  • ⚠ Field note (owner, 2026-06-30) — readiness still counts cover PER-TRACK. The Project Portal shows "Cover approved 1/5" — the doc-32 readiness rollup (computeReleaseReadinesstrackApprovedCount('cover')) treats cover as a per-track dimension, which contradicts the locked decision below (cover = release/project art, not per-track). To reconcile later: for Album/Compilation projects, the cover readiness step should be project-level (one project cover) not an N-of-tracks count; for Single, the track cover = the single's art (today's count is fine). Owner flagged as "consider afterwards" — captured here, not yet fixed.
  • Cover ownership follows the release. (Owner reconfirmed 2026-06-12: "cover is more from the project rather than the track" — Project/release art is the primary concept; Project-Portal art ships in the doc-26 phase.) A cover is release art, so it attaches to the container that represents the release:
    • Project of type Single → the track's cover is the single's art (today's model — correct as-is).
    • Project of type Album/Compilation → the Project owns the album art, managed in the Project Portal (lands with Portals-everywhere, doc 26). Track-level cover then acts as an optional per-track override (e.g., a focus-track single art inside an album).
    • Display preference everywhere: track cover → project art → identity-accent gradient.
  • With Option C's multi-release membership (appearsInProjects[]): a track appearing on several releases shows that release's art in that release's context — covers belong to the relationship's release side, never duplicated onto the track. (This is why cover ownership must sit with the Project/release before multi-membership ships.)

Decisions — confirmed 2026-06-10

  1. Model depth = Option C. variantType enum + bidirectional relations + secondary appearsInProjects[] on Track. Not a full Release/Work remodel (B), not labels-only (A).
  2. Multi-album membership = secondary appearsInProjects[] (canonical project stays the home; extra appearances are a lightweight M2M). Part of Option C.
  3. Timing = envision-only. Built after the current polish pass + the sharing system (doc 26). The cheap variantType tag was not pulled forward — it ships with this doc's phase, not the current polish.

When this is built it goes backend-contract-first (the enum + two array fields + mirrored-write/resolve logic on Track — Claude Code's part) then iOS (variant chips, group-by/cross-link lenses), per the handoff rule for backend-touching slices.

Cross-references

Ctrl-Audio Platform Documentation