# Session — 07 July 2026

## Scaffolded: v2 Recommendation Engine — code complete, not yet deployed (Systems A–E + Phase 4)

All six axioms encoded as running code. Every claim is evidence, not fact.
MusicBrainz is the structural spine, not truth. Conflicts coexist in the graph.

### Files created (8 new)

| File | Lines | System |
|---|---|---|
| `backend/src/services/generators.service.ts` | 446 | **C** — 8 candidate generators |
| `backend/src/services/session-director.service.ts` | 761 | **D** — Session planner + fatigue + arcs |
| `backend/src/services/discovery.service.ts` | 297 | **E** — Graph walks + probation lifecycle |
| `backend/src/services/image-enrichment.service.ts` | 105 | **Phase 4** — Image candidate pipeline |
| `workers/src/mb-spine-writer.ts` | 136 | **A** — MB artist-credit → claims writer (wired into enrichment.service.ts) |
| `backend/src/routes/graph.routes.ts` | 152 | Graph API (claims, fusion, sources, evidence) |
| `backend/src/routes/v2.routes.ts` | 130 | v2 vibe endpoints (start, next, feedback, state) |
| `backend/src/routes/discovery.routes.ts` | 103 | Discovery + image API endpoints |

### Files modified (3)

| File | Changes |
|---|---|
| `backend/src/db/schema.sql` | +187 lines: 9 new tables + 2 ALTER TABLE + indexes |
| `backend/src/services/db.service.ts` | +700 lines: 6 migrations, 12 new methods, 6 interfaces, evidence wiring in recordPlay/recordSkip/recordFeedback/dislikeTrack, listener-behavior writer in recordPlay |
| `backend/src/app.ts` | +4 lines: imports + registrations for v2 + discovery routes |

### System-by-system

**System A — Knowledge Graph (probabilistic fusion)**
- `source_trust` table: configurable trust weights (mb=0.90, tag=0.30, listener_behavior=0.40)
- `claims` table: graph spine, unique on (subject, pred, object, source, user_id)
- `claim_fusion` view: weighted vote SUM(trust × confidence × recency)
- `track_artists_v2` / `album_artists_v2`: compatibility views over fusion
- `recording_mbid` on tracks: structural spine anchor
- Methods: upsertClaim, upsertClaims, getClaimsBySubject, getFusedValue, getFusedTrackArtists
- Backfill migration `20260707_backfill_claims`: existing track_artists → claims (tag), artist_similar → same_scene_as (lastfm)

**System B — Listener Model**
- `evidence` table: append-only signal stream
- `listener_beliefs` table: per-profile beliefs with decay
- Every play/skip/feedback writes evidence rows automatically
- Methods: recordEvidence, recordEvidenceBatch, getListenerBeliefs, updateListenerBelief

**System C — Candidate Generators (8 generators)**
- `comfortGenerator`: longterm affinity > 0.5 artists
- `adjacentGenerator`: 2-hop graph walks from seed artist
- `discoveryGenerator`: unfamiliar artists via same_scene_as from trusted artists, gated by novelty_tolerance
- `deepDiveGenerator`: obsession album deep cuts in album order
- `revivalGenerator`: stale high-affinity artists (>90d untouched)
- `experimentalGenerator`: random unfamiliar genres
- `contextualGenerator`: context-tagged preferences
- `noveltyGenerator`: recent releases (≤60d) via same_scene_as/same_label_as/produced edges from trusted artists
- All candidates carry non-empty `ClaimEdge[]` explanations (graph paths)

**System D — Session Director (runs alongside v1; getNextVibeChunk not yet deleted)**
- `buildState`: energy from last 5 plays, novelty_hunger from discovery profile, session age
- `computeFatigue`: exponential decay per dimension (track/7d-30d, artist/24h-8h, genre/24h-8h, language/2h-1h)
- `getBudgets`: reads diversity_budgets, calculates spend from recent history
- `pickArc`/`getArcSlots`: energy+novelty-based arc templates (comfort/discovery/energetic/late-night)
- `rankCandidates`: multi-objective weighted sum (enjoyment, fatigue, diversity, entropy, repetition)
- `detectAntiLoop`: Herfindahl-Hirschman Index + fatigue threshold
- `buildPlan`/`replan`: full orchestration loop with slot filling
- 27KB of planner logic

**System E — Acquisition Pipeline**
- `walkGraphForDiscovery`: walks same_scene_as/featured_on edges to artists not in library
- `evalCandidates`: fused relevance + novelty tolerance + diversity check → acquire/retire
- `evalProbation`/`sweepProbation`: evidence-based retain/retire lifecycle
- `runMetaLearning`: discovery source retention analysis
- `discovery_candidates` + `probation_status` columns

**Phase 4 — Image Candidates**
- `fetchImagesForArtist`/`fetchImagesForAlbum`: write candidate rows per source
- `selectBestImage`: source-priority-tiered selection, updates image_path/artwork_id
- `image_candidates` table with source/verified tracking

### Evidence wiring (every interaction)
- recordPlay → playback_completed (longterm +0.10) + replay_within_24h if applicable + alias_of/same_scene_as behavior claims
- recordSkip → skip_quick (negative -0.20)
- recordFeedback(promoted) → add_to_favorites (longterm +0.60)
- recordFeedback(disliked) → hidden (negative -0.60)
- dislikeTrack → hidden (negative -0.60)

### Verification
- `npx tsc --noEmit` — 0 errors
- `npx vitest run` — 30/30 pass (mocked shape checks, not DB-state)
- No git repo — changes uncommitted
- NOT deployed: live backend container is pre-v2; `/api/v2/*` and `/api/graph/*` return 404; DB has zero v2 tables. See `docs/architecture/v2-fix-plan.md` for the fix + deploy plan.

### Next
- Execute `docs/architecture/v2-fix-plan.md` (MV refresh, decay job, bug fixes, deploy)
- After deploy + verify: wire the v2 endpoint into the frontend Vibe page (replace v1 vibeService calls)
- Build yt-dlp worker for System E acquisition (download candidates)
- After v2 is verified in production: delete v1 CTE (`getNextVibeChunk`), `vibe.routes.ts`, `feedback` table, `artist_similar` table per the doc's "Retiring v1" list
