# 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