docs: Phase 9 Wave 2 landed — move 9.2 from PLAN to COMPLETED

This commit is contained in:
daniel-c-harvey
2026-06-12 22:26:28 -04:00
parent 46749c8fa4
commit 5f7eaed112
2 changed files with 21 additions and 20 deletions
+21
View File
@@ -8,6 +8,27 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
## Phase 9 — Release Medium Types
### 9.2 Wave 2 — API: medium reads + metadata uploads
**Landed:** 2026-06-12 on dev.
A new `api/release` controller — the medium unit is the *release*, not the track, so medium browse and metadata uploads are release-cardinal rather than bolted onto `api/track/page`.
- **9.2.A — Release read endpoints (data layer + controller).**
- **What:** `GET api/release?medium={cut|session|mix}&page=&pageSize=&sort=` (unauth, paginated, medium filter additive — omitting returns all) and `GET api/release/{id}` (unauth, single release + medium metadata). The **list** read `Include`s the matching metadata table via a per-medium projection map; the **by-id** read always-`Include`s both metadata navs (two 1:1 unique-FK joins; non-matching media naturally yield nulls — no per-medium branching, no map).
- **Why:** The public CUTS/SESSIONS/MIXES surfaces and the CMS browsers all read releases by medium. One cohesive release-read family keeps `api/track/page` focused on Phase 8's track-list cases.
- **Shape:** Repository/service join through the metadata tables only for the relevant medium on list reads; base release reads never touch them. The projection map carries a dual responsibility: per-medium `Include` selection *and* the single enforcement point of the medium↔metadata correlation (a metadata DTO is populated iff the medium matches) — which is why it is not inlined in the controller. The honest extensibility guarantee is "one entry, one file," not "zero controller changes." `ReleaseDto` gains `Medium`, a **nullable** `ReleaseType?` (nulled at the mapping point for non-`Cut`), and optional nested `SessionMetadataDto?` / `MixMetadataDto?` (populated only for the matching medium — mirrors Phase 8's nested-`Release` choice, not denormalized flat fields).
- **Acceptance criteria:** `GET api/release?medium=session` returns Session releases with hero-image metadata included and no `MixMetadata`; `medium=cut` returns Cuts with neither metadata block and a non-null `ReleaseType`; non-Cut releases serialize `ReleaseType: null`; pagination + sort parity with `api/track/page`.
- **9.2.B — Metadata write endpoints.**
- **What:** `POST api/release/{id}/session/hero-image` (ApiKey, multipart — hero image → image vault → set `SessionMetadata.HeroImageEntryKey`) and `POST api/release/{id}/mix/waveform` (ApiKey, **no request body** — a server-side trigger: the API fetches the mix audio from its own vault, computes the high-resolution waveform via `WaveformProfileService` parameterized by resolution, stores the datum in the vault, sets `MixMetadata.WaveformEntryKey`). Both routes are resource-addressed — the release id rides the route.
- **Why:** The CMS authoring flows (Wave 3 B/C) need write paths for the medium-specific data, and the waveform is a *derived* datum the server can compute from audio it already owns. Mirroring the existing body-less `POST api/track/{trackId}/waveform` idiom makes the datum correct by construction (no trusting a client blob) and keeps the CMS free of any in-process data layer (its standing constraint). Splitting these from the track-upload endpoint keeps each endpoint single-responsibility.
- **Shape:** Hero-image upload mirrors the existing cover-art `UploadImageAsync` → image-vault → link pattern, targeting `HeroImageEntryKey`. The waveform trigger includes the `WaveformProfileService` refactor: a per-call resolution/profile parameter (today fixed via injected `WaveformProfileOptions.BucketCount = 512`) plus a distinct entry-key/vault target for the high-res datum — one pipeline, two resolutions (*One source, multiple views*). Both endpoints find-or-create the metadata row for the release.
- **Acceptance criteria:** Posting a hero image to a Session release sets `HeroImageEntryKey` and the image is served back through the existing image proxy; the body-less waveform trigger on a Mix release computes + stores a high-res datum, sets `WaveformEntryKey`, and the datum is retrievable.
**Completion note:** Five new endpoints on `ReleaseController` implemented and integrated. `ReleaseRepository` + `ReleaseManager` (`IReleaseService`) in `DeepDrftData` provide paged medium-filtered reads and satellite metadata writes. `UnifiedReleaseService` orchestrates vault + SQL operations in `DeepDrftAPI/Services/`. `ReleaseDto` updated with `Medium` field and nested `SessionMetadataDto?` / `MixMetadataDto?` properties. Per-medium projection map enforces medium↔metadata correlation at the single mapping point. `WaveformProfileService` refactored with optional `bucketCount?` and `vaultName?` parameters supporting multiple resolutions. `VaultConstants.MixWaveforms = "mix-waveforms"` added. Five endpoints serve reads (`GET api/release` with medium filtering and pagination, `GET api/release/{id}` with both metadata tables included) and writes (`POST api/release/{id}/session/hero-image`, `POST api/release/{id}/mix/waveform`). All acceptance criteria met; Wave 3 (CMS) now unblocked.
---
### 9.1 Wave 1 — Data model + migration
**Landed:** 2026-06-12 on dev.