Skip to content

32 — Approval as a First-Class Primitive (dimensional approval across the catalog)

Envisioning doc — needs confirmation. Governed by 00-ROADMAP.md. Created 2026-06-13 (owner-raised). Generalizes 29-VERSION-SCOPED-APPROVAL.md (audio-version approval, built) into a full model: approval is a dimension, scoped to the node where the thing being approved actually lives, surfaced in Portals (doc 26) — not only in the Wave player. Folds in the owner's cover-art thread (crop, hero, history-CRUD, where cover approval lives). Backend v1 BUILT 2026-06-20 (decisions 1 & 3 confirmed; see §Status). The rest stays envisioned.

Status — backend v1 shipped 2026-06-20 (Claude Code; nest build clean, jest 14/14, boot-verified). Confirmed scope = cover (per-image) + tracklist + release-gate rollup, per owner.

  • Cover = a per-image approved dimension (decision 3 = per-image, doc-29 pattern): the existing PATCH /track/:trackId/file/:fileId/review-status now mirrors an image final → releaseStatus.cover (audio still → releaseStatus.version). Honesty hooks: a fresh cover upload → cover Under review; setCoverImage promote → cover badge follows the promoted image's verdict.
  • Cover-history DELETE (§7.3): DELETE /track/:trackId/image/:fileId — soft-delete an image version, guarded (refuses the current imageFinal; promote another first).
  • Tracklist dimension: Project.tracklistStatus {status,by,at} + PATCH /project/:id/tracklist-status (owner/admin). (Reset-on-reorder deferred — no reorder endpoint is exposed yet.)
  • Release-gate rollup: GET /project/:id/release-readiness — read-time compute (every track's final audio + cover Approved, tracklist locked → ready + per-track breakdown). No stored truth. ⏳ Remaining: the iOS Portal approval sections (cover approval in Project/Track Portal, tracklist lock, the release-gate headline, Status-lens surfacing) + cover-history browser; and the still-envisioned dimensions (lyrics, master, production-phase marker) + review-history log + quorum (decisions 2,4,5,6).

What triggered this

The owner, reviewing the cover slice on TestFlight, connected several threads into one:

"Approval now is only in Wave… cover I believe should be more on the Single / Project / CD area… and approval should be considered also in lyrics, tracklist & order, edit or production phases, moreover mastering… this needs to be grounded and envisioned with our whole vision of the project, use cases, actors and objectives."

Plus the concrete cover observations (folded in §7): a cover bigger than the frame isn't cropped; the cover isn't shown in the hero; the cover history should be a CRUD (change or delete from history).

Why this matters (objective tie)

Approval is not a feature — it is the core verb of the product.

  • It's the MVP loop. "Review & Approve" is the minimum lovable slice (00-ROADMAP.md §5). Everything else is in service of someone, somewhere, saying yes, this one.
  • It's a brand pillar. Professional calm — truthful state (31-PRODUCT-VISION-360.md): "an Approved badge may never lie about which audio it approved." Doc 29 made that true for audio versions; the same honesty must hold for every other thing a badge can claim is approved.
  • It's the B2B accountability layer. Labels/managers (doc 31's center of gravity) live or die by sign-off chains: who cleared the lyrics, who approved the master, who locked the tracklist. Captured approval (by / at, with history) is the artifact that makes Sonnance the place where "who-approved-what is born correct," upstream of distribution.
  • It feeds the release gate. The distribution workflow (12-ACTORS-WORKFLOWS.md §"Distribution Integration") already validates "approved version, metadata complete" before submitting a release. A dimensional approval model is exactly what that gate reads.

What exists today (ground truth, verified against the schema)

  • Audio-version approval — built (doc 29). File.reviewStatus (+reviewStatusBy / reviewStatusAt), per-file PATCH /track/:trackId/file/:fileId/review-status; the track-level mirror releaseStatus.version reflects the final version; new upload → Under review.
  • The codebase already thinks in dimensions. ReleaseDimension { TITLE, VERSION, COVER } and ReleaseStatus { title, version, cover } on the Track (status.type.ts) — three approvable axes, each a TrackReleaseStatus (No status | Under review | Not approved | Approved). This is the seed of the whole model. Doc 29 already proved one axis (version) can be re-homed onto the node where it really lives (the File). We generalize that move.
  • Cover lives in two places. Track.image[] is a history with imageFinal (exact mirror of audio[]/audioFinal); setCoverImage promotes an existing image to cover. Project.image + Project.type exist — Project already can own album art (doc 28: cover is primarily a release/project concept). What's missing: surfacing cover approval anywhere but the player, and a delete-from-history verb.
  • Surface today = the Wave player only. The three pills (Title · Version · Cover) live on the player's ApproveBar. That's why the owner says "approval is only in Wave."

The gap

Approval is bound to one node (audio) and one screen (the player), but a release is approved along many dimensions, each owned by a different node, each signed off by a different actor, in different surfaces. A cover isn't approved while you're scrubbing a waveform; a tracklist order isn't approved on a single track; a master is a distinct deliverable with its own sign-off; lyrics may need a legal clearance, not an artistic one. Forcing all of that through the player's three pills is both wrong-place and under-modeled.


The model: approval is a dimension, not a screen

One primitive, applied everywhere:

An Approval is { status, by, at } attached to a (node, dimension) pair. status reuses the existing TrackReleaseStatus enum. The dimension is scoped to the node where the thing it approves actually lives — never duplicated, never ambiguous (the doc-29 lesson). The Wave player is just one surface onto one dimension; Portals (26 Layer 4) are the home for the rest.

The dimensions

DimensionLives on (scope)Typical approverSurfaced inStatus today
Audio versionFile (per version)Artist / ManagerWave player + Version/Track Portal✅ built (doc 29)
Title / namingTrackManager / ArtistTrack Portal✅ track-level field
Cover artProject/release (primary) · Track (override)Creative Director / Manager / Visual ArtistProject + Track Portal (not Wave)🟡 field exists, wrong home/surface
LyricsTrack (or the specific version)Artist / Manager · Legal (clearance)Track Portal / Lyrics attachment❌ net-new (doc-57 §5.3's synced-lyrics preview does not create this dimension — lyric sign-off remains a separate, later extension here)
Tracklist & orderProjectManager / Artist / A&RProject Portal❌ net-new
Metadata & creditsTrack + ProjectManager / LegalPortals🟡 fields exist, no sign-off
Master (mastering)the master File · a phase gateMastering Engineer delivers → Artist/Manager approveTrack Portal / Wave❌ net-new (a phase gate)

Invariant (carried from doc 29): the unit of approval is the exact thing the approver was looking at. Audio → the File you heard. Cover → the image version you saw. Lyrics → the lyric revision. Never a vague "approved" floating above an ambiguous set of versions.

Production phases (the lifecycle ladder)

The owner named "edit or production phases, moreover mastering." Real catalogs move a track through a pipeline — Writing → Recording → Production/Arrangement → Mixing → Mastering → Release — and each phase has its own gate (doc 12's Draft → InReview → NeedsRevision → Approved state machine, but repeated per stage). Two ways to model it:

  • A — Explicit phase marker on Track (enum) that advances when the relevant dimension is approved. Honest, queryable ("show me everything in mastering"), but a new state to keep in sync.
  • B — Derived phase — compute "where is this track" from which dimensions are approved. No new field, but fuzzy (a track can be mid-mix and mid-art at once).
  • Recommendation: hybrid — a lightweight phase marker on Track (where in the pipeline this is) whose transitions are gated by the dimensional approvals. The marker is the lens label; the approvals are the truth. (Decision flagged below.)

The release gate (the rollup)

A Project (a release) is ready to release when all its required dimensions are green: every track's final audio Approved, cover Approved, tracklist locked, metadata complete. This is a read-time computation over the dimensional approvals — not a new stored truth — and it is exactly the precondition the distribution workflow already wants (doc 12). It becomes the Project Portal's headline state ("3 of 9 tracks awaiting approval · cover approved · tracklist locked") and, later, the green light that arms "Distribute."


Actors × dimensions (who signs off what)

Grounded in the real actor set (12-ACTORS-WORKFLOWS.md). Roles still gate — owner/admin can record an approval; the captured by is the meaningful sign-off identity.

ActorPrimarily approves
Artisttheir audio versions; lyrics (artistic); final masters of their work
Producer / Engineerdelivers versions & masters for approval (not self-approver in B2B chains)
Mastering Engineerdelivers the master; marks it ready
Managerrelease-level gates: cover, tracklist, metadata, "ready to release"
Creative Director / Visual Artistcover art / visual assets
A&Rtracklist & sequencing; go/no-go on the release
Legallyrics clearance, credits/splits sign-off (a clearance gate, distinct from artistic approval)

This table is why approval can't live only in the player: each actor works in a different surface, and the manager/A&R/legal sign-offs are the B2B value (doc 31) — they don't happen while scrubbing a waveform.

Where it surfaces

  • Wave player keeps the audio-version verdict — the visceral approve-while-listening moment, a brand moment (doc 22). It stays; it's just no longer the only place.
  • Portals everywhere (26 Layer 4) host every other dimension: the Track Portal shows title/lyrics/master approvals; the Project Portal shows cover/tracklist/metadata
    • the release-gate rollup. (Project Portal already exists — Wave-2 slice 8 — so cover approval has a home now.)
  • Status lens (26 Layer 2) filters across all dimensions: "awaiting my approval," "needs revision," "cleared for release" — per-actor default views fall out for free.

Review history (per dimension)

The owner wants cover history to be a CRUD, and doc 29 already flagged "review history per file" as a future need. Generalize: each (node, dimension) keeps an append-only verdict log{ status, by, at } over time — so "Approved by Ana, Jun 7 → Revision requested by Lee, Jun 9 → Approved by Ana, Jun 11" is recoverable. This is the audit artifact B2B teams need and the basis for "who approved what" attribution (doc 31). (Embed-per-node vs a generic activity/audit collection is a decision below.)


§7 — Cover image management (the owner's concrete thread, grounded)

Four items, three immediate-ish and one explicitly deferred:

  1. Cover not cropped to frame — uploads aren't fit to the artwork frame, so an oversized image overflows. Near-term: client-side aspect-fill + a manual crop affordance on upload (no backend needed — Sharp already produces square-ish variants; the fix is the upload UX). Future phase (deferred — we're in beta): iOS 27's on-device generative tools for smart resize / outpaint and even cover generation. Real, valuable, not now — captured in 24-UX-BACKLOG.md and revisited post-beta.
  2. Cover not shown in the hero — the cover uploads but the hero still draws the gradient. Backend is healthy (image[]/imageFinal serialize; the artist/project image→ArtworkHero wiring shipped slice 9). This is the track-cover → hero (and project-cover → project-hero) wiring half that wasn't threaded — an iOS QA/wiring item (queued in next-brief.md), display-preference cascade per doc 28: track cover → project art → identity-accent gradient.
  3. Cover history = CRUDimage[] is the history; setCoverImage covers change (promote). Missing the delete verb (remove a stale image from history; never the current final without re-pointing) and the iOS history browser (the Portal's cover section lists image[], tap to promote, swipe to delete). Small backend add (mine) + an iOS slice.
  4. Where cover approval lives — move it off the Wave to the Single/Project/CD area (Project + Track Portal), per the dimensional model and doc 28 (cover = release art = a project concern). The releaseStatus.cover field already exists; this is a surface move plus an optional upgrade to the doc-29 per-image pattern (approve a specific cover version, not a track-level string) if cover review needs the same rigor as audio.

Backend map (reuse vs net-new)

CapabilityTodayNet-new
Audio-version approvalFile.reviewStatus (+by/at)none — doc 29 unchanged
Dimension shape {status, by, at}🟡 ReleaseDimension+releaseStatus on Trackgeneralize the doc-29 per-file pattern to each dimension
Cover approval in the Portal🟡 releaseStatus.cover existssurface in Project/Track Portal; optional per-image scoping
Cover history delete❌ (only setCoverImage promote)DELETE image-version verb (guard the current final)
Lyrics approval❌ (lyrics = attachment folder)lyrics dimension on Track/version
Tracklist & order + approval🟡 order derivableProject track-order field + a tracklist dimension
Master sign-offa master/phase-gate dimension (delivered → approved)
Production phaselightweight phase marker on Track (hybrid; decision)
Release-gate rollupread-time compute over dimensions (no stored truth)
Review history❌ (doc-29 "future")per-(node, dimension) verdict log (embed vs audit collection)
Status lens / "awaiting my approval"🟡 status filterableclient lens over the dimensions (doc 26 Layer 2)

How this sequences

A big new capability, like sharing (doc 26) and variants (doc 28): envision now, build later, ordered by what real-user feedback shows people reach for. Slot: after the doc-26 Shared waves (6–9), interleaved with the Teams mini-phase (doc 26 Wave 4) by demand.

Cheap ride-alongs that don't need the full build:

  • Cover approval in the Project/Track Portal — the Project Portal already exists (slice 8); surfacing the existing releaseStatus.cover there is a small, high-signal move.
  • Cover-history delete verb — small backend, completes the CRUD the owner asked for; pairs with the cover history browser.
  • Hero cover display — pure iOS wiring (QA item), no backend.

These three address the owner's immediate cover pain without committing to the dimensional remodel.

When the full model is built it goes backend-contract-first (generalize the approval shape + add the new dimensions/verbs — Claude Code) then iOS (Portal approval sections, Status lens, release-gate rollup), per the handoff rule for backend-touching slices.

Decisions — to confirm (not now; flagged)

  1. Dimension set for v1 — ✅ CONFIRMED 2026-06-20 = cover + tracklist (built). Lyrics/master after.
  2. Phase model — explicit phase marker (A), derived (B), or hybrid (recommended: marker gated by dimensional approvals)?
  3. Cover approval granularity — ✅ CONFIRMED 2026-06-20 = per-image (doc-29 pattern; built). The verdict attaches to the exact cover File; releaseStatus.cover is the back-compat mirror of the final.
  4. Review history storage — embedded per-node verdict array, or a generic activity/audit collection (reusable for the doc-26 activity lens too)?
  5. Release-gate authority — who can declare a Project "released," and does it lock further edits?
  6. Per-dimension reviewers / quorum — single approver vs required sign-offs (e.g., Legal must clear lyrics before the gate opens). A B2B feature — later, but the data model should not preclude it.

Until built, the doc-29 audio-version approval stands as the only formal gate; nothing here blocks the doc-26 Shared waves or the Teams mini-phase.

Cross-references

Ctrl-Audio Platform Documentation