---
type: plan
title: dailysplice — foundation and build plan
slug: dailysplice-2026
status: active
owner: rian
project: dailysplice
created: 2026-08-04
updated: 2026-08-04
status_note: F0 (foundation) complete and deployed; F1 (product definition) not started
summary: Build plan for an enhanced Spotify playlist builder that splices music
  tracks with user-created podcast episodes into daily playlists. F0 lays the
  foundation — v8 workspace, FastAPI + React + Postgres per house standards, BW
  Auth Pattern B with invite-only admission. F1 onward is the product itself,
  deliberately unspecified until rian fills §3.
---

# dailysplice — foundation and build plan

Genesis and known gaps: [`../../brief.md`](../../brief.md).
Session operating rules: [`../../agents.md`](../../agents.md).

This project has been started several times. This plan is the fresh start: it
gets a correct, standards-conformant, *authenticated* foundation deployed first,
and treats the product design as a separate, later act — so the next session
starts from something real instead of from scratch again.

---

## 1. Locked decisions

These are settled. Reopening one is a deliberate decision, not a drive-by.

| # | Decision | Why |
|---|---|---|
| D1 | **Stack: FastAPI + SQLAlchemy 2/Alembic + Postgres 17 + Vite/React 19 SPA** | The house standard (`react.md`) for an app needing a real DB, app-like UI, and a product path. Python owns logic and data; React owns pixels only. |
| D2 | **Auth: BW Auth Pattern B, PUBLIC domain mode** | The app has a genuinely public front and a signed-in area. PUBLIC is the Pattern B default; the app draws its own welcome page and a "Sign in" button. `coding.md` forbids a hand-rolled login. |
| D3 | **Admission is invite-only and enforced in the app** | PUBLIC mode means any BW account can complete the handshake. Access is a *separate*, default-deny decision made against an invite allowlist in the app's own DB. |
| D4 | **Roles: `superadmin`, `user`, `none`** | Minimal and sufficient. `none` is the explicit not-invited state (default-deny is representable, not implicit). `rian` is `superadmin`. Roles are the app's authority; `report_role` only mirrors them to the BW hub for display. |
| D5 | **Postgres from day one, no published port** | `react.md`: never "SQLite now, swap later". The DB is on an internal compose network only, reachable via `docker exec`. |
| D6 | **No service worker / PWA** | The SW navigation fallback silently swallows the OAuth callback (a documented garden2 failure). Nothing here needs offline. Adding one later carries the denylist requirement in `agents.md`. |
| D7 | **Spotify is a third-party integration, not an identity provider** | Users sign in with BW. Spotify authorization is a *separate* per-user linkage the user grants later, so a user exists before a Spotify account is attached. |

## 2. F0 — foundation (COMPLETE)

The point of F0 is that the next session never has to redo any of it.

- [x] v8 workspace scaffolded (`new-workspace.sh`); old nginx scaffold archived.
- [x] `agents.md` / `brief.md` / this plan written; `CLAUDE.md` symlinked.
- [x] FastAPI backend, structured `app/{routers,services,models}` — no monolith.
- [x] Postgres 17 sidecar, internal network, no published port; Alembic initial
      revision creates `app_accounts` + `invites`.
- [x] BW Auth Pattern B wired: `bw_auth.py` drop-in, PKCE handshake, app-signed
      session cookie, client-driven silent probe, full logout.
- [x] **Default-deny `AuthMiddleware`** over `/api/*` with a small exact-path
      public allowlist. No prefix exemptions.
- [x] Invite-only admission in `services/identity.py`; roles seeded by invite.
- [x] React SPA: public landing, sign-in, signed-in shell, not-invited screen,
      version in the footer.
- [x] 3-stage Docker build (OpenAPI dump → TS client + SPA build → Python
      runtime). Generated TS client is never committed.
- [x] Deployed at <https://dailysplice.com>, host PUBLIC, sign-in verified.
- [x] Auth-boundary tests (pytest) — the gate is the thing worth testing so far.

**F0 gate:** `main/scripts/gate-f0.sh` exits 0. It asserts the version endpoint
matches `VERSION`, `/api/me` is anonymous-safe, a gated route 401s without a
session, and the DB has both tables.

## 3. F1 — product definition (NOT STARTED — fill this with rian)

**This section is deliberately empty.** Guessing it is how this project stalled
before. It gets filled in a working session with rian, not inferred. The open
questions, in the order they block code:

1. ~~**What is a "user-created podcast episode"?**~~ **ANSWERED 2026-08-05.**
   Not a user upload — an **AI/TTS-generated DJ segment**, written per user and
   spliced between tracks: track tidbits, and relayed context from connected
   people ("your dad has been listening to Nick Cave because…"). Plus a timeless
   variant: curated appreciation splices ("understanding jazz"). The delivery
   mechanism is settled too, and not the way it was first imagined — **not RSS
   into Spotify**, which is impossible (§3c). See §3c for the full analysis.
2. **What does "daily" mean mechanically?** One playlist per user per day,
   regenerated at a fixed local time? A single rolling playlist mutated in
   place? This decides the scheduler and the Spotify write pattern.
3. **What are the splice rules?** Ratio of music to spoken, ordering, segment
   length, whether spoken breaks are pinned to positions or distributed. Write
   them as rules a person can read before writing a generator.
4. **Whose Spotify account holds the playlist?** Each user's own (per-user OAuth
   + playlist write scope), or one app-owned account? Different consent, storage,
   and rate-limit shapes.
5. **What is the social surface, if any?** Do users hear each other's episodes?
   That is the difference between a personal tool and a small network, and it
   changes the data model.

Nothing in F1 gets built until §3 has answers.

**Question 1 is now answered (see §3c), and it moved the architecture:** the
product is not "a playlist with podcast items in it" but **a player that
interleaves streamed music with generated speech**. Questions 2–5 remain open,
but 4 ("whose account holds the playlist?") is largely dissolved by that shift —
there may be no playlist to hold.

## 3b. The Spotify access wall (researched 2026-08-05 — read before planning F2)

This is the single largest external constraint on the project, and it is worse
than it was when the idea was first sketched. Everything below was checked
against Spotify's own developer documentation and announcements on 2026-08-05.
**Re-check before acting on it** — this policy has moved three times in
eighteen months, always tighter.

### What "connect anyone's Spotify account" costs today

| | Development Mode | Extended Quota Mode |
|---|---|---|
| Users | **5**, each added by hand in the dashboard | unlimited |
| Who qualifies | anyone with a Spotify app | see below |
| App owner | must hold an **active Spotify Premium** subscription, or the app stops working | — |

**Extended quota criteria (tightened March–May 2025):** a legally registered
**business or organization** (individuals may no longer apply), an **active,
launched service**, availability in key Spotify markets, and a minimum of
**250,000 monthly active users**. Applications go through a form, reviewed in up
to six weeks.

That last row is the wall, and it is circular: **you need 250,000 monthly active
users to be permitted more than five users.** There is no growth path from 5 to
250,000 through the Web API. Treat "a public Connect to Spotify button" as
**not achievable on the current policy**, not as a milestone that arrives later.

### The API surface also shrank

- **27 Nov 2024** — new apps permanently lost: **Recommendations**, **Audio
  Features**, **Audio Analysis**, Related Artists, Featured Playlists, Category
  Playlists, 30-second preview URLs, and algorithmic/editorial playlists. Apps
  that already had extended access kept them; new ones cannot get them at all.
  **This removes the obvious way to build a smart mix** — no tempo, energy,
  danceability, or Spotify's own recommender. Any splice logic has to be ours.
- **February 2026** — Development Mode lost more: batch fetch endpoints
  (`GET /tracks`, `/albums`), browse and artist endpoints, and `GET /me/tracks`
  (saved library). `popularity` and other fields were dropped. Search is capped
  at **10** results (was 50). Playlist `/tracks` became `/items`.
  Confirmed still available: `GET /me`, list/create playlists, and read/write
  playlist items — which is exactly enough to build and write a playlist, and
  not much more.

### What this means for the product

- **The good news, and it is real:** playlists **can** contain podcast episodes.
  `POST /playlists/{id}/items` accepts `spotify:episode:` URIs alongside
  `spotify:track:`. The core "splice music with spoken audio" mechanic is
  supported by the API.
- **The hard constraint that follows:** an episode can only be in a Spotify
  playlist **if it is on Spotify**. A user-created episode has to be published
  through a podcast host that distributes to Spotify (Spotify for Creators is
  free) before this app can splice it in. This is a *product* decision disguised
  as a technical one, and it is §3 question 1. Ingesting raw audio uploads and
  hoping to splice them into a Spotify playlist **cannot work**.
- **Track selection is ours to build.** With no Recommendations or Audio
  Features, the music side comes from the user's own playlists plus search, and
  the ordering logic is entirely this app's — which is arguably better for a
  product whose value is the splice rules, but it is real work that was not on
  the sketch.

### Why services like Soundiiz still work: they were grandfathered

Checked 2026-08-05, because "Soundiiz connects to my Spotify and surely does not
have 250k users" is the obvious objection and it deserved a real answer.

**Soundiiz was founded in January 2013** — twelve years before the door closed.
Spotify's own announcement of the May 2025 change says: *"Developers previously
granted extended access and actively using the Web API in compliance with our
Developer Terms will remain unaffected by this change."* The same grandfathering
applied at the February 2026 cut: existing apps kept their client IDs and their
users.

So the objection is correct on the facts and points the wrong way. Soundiiz can
do this **because it got in before the rule existed**, not because the rule is
soft. Building the same product today does not get the same access. There is no
inference from "that app can" to "a new app can".

### Does an OLD, unused development-mode app help? Almost certainly not

Asked 2026-08-05 (rian has a dormant Spotify app from an earlier experiment,
probably created before February 2026). The answer turns on one sentence about
the February 2026 cut:

> *Existing apps are grandfathered: if you already have multiple Client IDs or
> more than 5 users, you will retain them. These limits only restrict what you
> can create or add going forward.*

**Grandfathering preserves state, not entitlement.** It keeps users you had
*already added*. An app that was created and then never used has no users to
keep, so it inherits nothing — it gets the same 5 as a brand-new app. Age alone
buys nothing.

Two separate things are also worth not confusing:

- **Extended-quota grandfathering (May 2025) does not apply at all.** That
  protects apps *previously granted extended access*. A development-mode app was
  never granted it, however old it is.
- **The November 2024 endpoint cut is a different question with a real payoff.**
  It targeted *new* use cases, and an app predating 2026-11-27 may have kept
  Audio Features, Audio Analysis and Recommendations — precisely the endpoints a
  playlist builder wants and that no new app can obtain. Reporting is
  inconsistent about whether development-mode apps kept them.

**Do not reason about this — measure it.** `main/scripts/spotify-probe.py`
answers it in one run: client-credentials flow (no user login, no browser), then
probe each endpoint and report what this specific app can actually reach. If it
retains the 2024 endpoints, that app is genuinely valuable and must not be
deleted or replaced. If not, the old app offers nothing and any app will do.

The allowlist size is dashboard-only (*User Management*); the empirical test is
to try adding a sixth user and see whether it is refused.

### The timeline, so the direction is unmistakable

| Date | Change |
|---|---|
| 27 Nov 2024 | New apps permanently lose Recommendations, Audio Features, Audio Analysis, Related Artists, Featured/Category playlists, preview URLs |
| 15 May 2025 | Extended access restricted to "established, scalable, impactful" use cases. **Individuals may no longer apply.** Organizations only; ≥250k MAU |
| 6 Feb 2026 | Development Mode cut to **5 users**, 1 client ID, owner must hold **Premium**, endpoint set reduced |
| 23 Jul 2026 | Client IDs per developer raised 1 → 25, but **quota shared under a single budget** |

Every step tightened. Spotify states the intent plainly: Development Mode is for
"learning, experimentation, and personal projects for non-commercial use by
individual developers" and **"should not be relied on as a foundation for
building or scaling a business on Spotify."** That is not ambiguity to be
lawyered — it is the platform saying what it is for.

**One thing genuinely unresolved:** whether the 5-user cap is per client ID (so
25 apps = 125 users) or shared across a developer's budget. The July changelog
does not say. Worth asking Spotify directly — but note the "shared budget"
wording reads like Spotify anticipating exactly that workaround, and 125 users
is still not a consumer product. Do not build a plan on it.

### Audio features are RECOVERABLE — verified live 2026-08-05

Losing `audio-features` looked like it killed any "start slow, build to fast"
ordering. It does not. Spotify removed the endpoint; it did not remove the
underlying facts, and a replacement ecosystem grew after the 2024 deprecation.

**ReccoBeats (`api.reccobeats.com`) — tested against the live API, works.**
It returns the **exact Spotify feature schema** — `tempo`, `energy`, `valence`,
`danceability`, `acousticness`, `instrumentalness`, `liveness`, `loudness`,
`key`, `mode`, `speechiness` — and, critically, **looks up by Spotify track
id**, which is what this app will already be holding:

```
GET https://api.reccobeats.com/v1/audio-features?ids=<spotify_id>,<spotify_id>,…
```

Verified: batches of 20 ids returned features for 40/40 ids, free, no auth,
no registration.

**Two things measured that must not be forgotten:**

1. **That 40/40 is a BIASED sample** — the ids were harvested from ReccoBeats'
   own index, so of course they were present. It proves the *mechanism* and the
   batching, **not catalogue coverage**. Real coverage has to be measured
   against an actual user library before any splice rule depends on it. Design
   the engine to **degrade gracefully on a missing feature** rather than assume
   full coverage.
2. **It 403s a default `Python-urllib` User-Agent.** Set an explicit
   `User-Agent` header or every call fails in a way that looks like a
   catalogue miss. (Cost a confusing 0/14 run before it was spotted.)

**Secondary sources, if coverage proves thin:**

- **Deezer** — public, **auth-free**, no registration. `GET /track/isrc:<ISRC>`
  matches straight off the ISRC that Spotify returns in `external_ids`, and
  gives `bpm` + `gain`. Measured **7/12** on a quick sample — partial, and BPM
  only, but free and a good cross-check.
- **AcousticBrainz** — a one-time public dump (July 2022, ~7.5M tracks, ~120
  low-level descriptors). Frozen: nothing released after mid-2022. Good for back
  catalogue, useless for new music.
- **Essentia** — the open-source toolkit Spotify itself used to derive most of
  these values. The fallback if audio is ever in hand.

**Consequence for the plan: the splice engine's musical intelligence is NOT
gated.** Tempo curves, energy arcs, valence journeys — all buildable, at any
scale, for free. What is gated is only the final write into a stranger's Spotify
account. Keep those two problems separate; they have different answers.

### What is NOT blocked (this matters more than it first looks)

**Podcast distribution to Spotify has nothing to do with the Web API.** A show
is submitted as an **RSS feed** through Spotify for Creators — a separate,
open, free system with no quota, no allowlist, and no MAU test. So:

- DailySplice hosting podcasts and having them appear on Spotify: **works.**
- Episodes being addable to a playlist: **works** (`spotify:episode:` URIs).
- Users *listening* on Spotify: **works.**

The blocked operation is narrow and specific: **programmatically writing a
playlist into an arbitrary user's Spotify account.** Everything else in the
product survives.

### Options, honestly

1. **Five users, hand-allowlisted (what is built now).** Fits invite-only
   exactly. Enough to build, prove the splice rules, and use it personally. The
   right move for now regardless of what is decided about scale.
2. **Reconsider platform-exclusivity — the recommended strategic change.** The
   splice engine, podcast hosting, RSS distribution and taste model are all
   platform-neutral. Only the final "write the playlist" step is not. Put that
   behind a `MusicProvider` interface and Spotify becomes one implementation
   rather than the foundation. Verified alternatives that permit public apps
   today:
   - **Apple Music (MusicKit)** — explicitly supports "create or modify
     playlists" with user authorization. Requires Apple Developer Program
     membership; no published user cap or quota-extension gate. Strongest
     alternative.
   - **YouTube Music (YouTube Data API v3)** — playlist create/insert works;
     10,000 quota units/day by default, a write costs ~50, and **quota
     increases are requestable and free**. A real, documented growth path.
   - **TIDAL** — official developer API with authorization-code flow and
     playlist endpoints, actively expanding.
3. **Ask each user to register their own Spotify app.** Removes the cap because
   each user is their own app owner — but every one of them needs Premium *and*
   a developer account. Viable for a technical audience, dead for "the average
   user". Record it; do not plan on it.
4. **Own the whole stream (music + talk) under a webcasting licence.** The only
   way to have music and spoken audio in one feed DailySplice controls, rather
   than assembling someone else's catalogue. This is the internet-radio regime
   (SoundExchange statutory licence in the US) — real, but heavy: licence fees,
   reporting obligations, and constraints on skipping and track ordering. Named
   here because it is the honest answer to "what if I depend on no platform",
   not because it is a sensible first move.
5. **Pursue extended quota.** Requires a registered company *and* 250k MAU.
   Unreachable from zero. Only becomes a real conversation if the product has
   already succeeded somewhere else.

**Decision for now: option 1, with option 2 as the architectural stance.** F2 is
built against Development Mode — five users, allowlisted by hand, the same
population as the invite list, so the two lists just stay in step. The provider
abstraction is not built yet and should not be built speculatively; the trigger
for it is the moment a second platform is actually wanted. But **no Spotify
type, field or endpoint should leak into the splice engine**, so that the
abstraction remains a cheap change rather than a rewrite.

**The platform-risk lesson, recorded once:** a product whose core value requires
one company's API is one policy post away from being unshippable. That already
happened here, twice, before a line of product code was written.

## 3c. The personalized-podcast design, stress-tested (2026-08-05)

The concept: a scheduled playlist mixing music with **per-user AI/TTS episodes** —
a DJ that introduces tracks, relays what a connected person (e.g. a parent) has
been listening to and why, and takes the listener somewhere. Plus a second,
timeless variant: curated **appreciation splices** ("understanding jazz", "a
lifetime of Nick Cave") that are shareable rather than personal.

Checked for showstoppers. There is **one**, it is decisive, and it only applies
to one of the two variants.

### SHOWSTOPPER: a per-user podcast cannot reach Spotify. At all.

**Spotify does not let a listener subscribe to an arbitrary or private RSS
feed.** This is long-standing, deliberate, and repeatedly declined as a feature
request. Spotify's own paid-podcast private feeds are explicitly usable *only in
other apps*, not inside Spotify.

So the only way an episode reaches Spotify is as a **public show in the
catalogue**, submitted through Spotify for Creators. For per-user personalized
episodes that means, all at once:

1. **A privacy failure.** "Your dad Jim has been listening to Nick Cave and was
   moved by his family's story" becomes a publicly searchable Spotify episode.
   Not a bug to fix later — it is what publishing to a catalogue *means*.
2. **No programmatic path.** Show creation is a manual dashboard act. There is
   no supported API for spinning up thousands of shows.
3. **It looks exactly like spam.** Mass-uploaded AI-generated shows are the
   precise behavioural pattern Spotify's 2025–26 enforcement targets. Their
   stance is permissive about AI *per se* and aggressive about upload patterns.
4. **The timing does not work anyway.** Spotify polls RSS on its own schedule —
   hours, not seconds. A "daily" show racing a feed crawler loses.

**This kills RSS-into-Spotify. It does not kill the idea.**

### The fix is a better product: DailySplice becomes its own player

Stop trying to hand Spotify a podcast. Make the episodes ordinary audio files
that DailySplice plays itself, interleaved with music streamed from the user's
own subscription. Then:

- No RSS, no catalogue submission, no ingestion latency, no spam signal.
- Personal content stays **private**, which is the only acceptable answer to §1.
- And the experience gets *better*: a DJ can talk **over an intro**, duck under a
  track, cross-fade, and react to what just played. A playlist with episode items
  wedged between songs cannot do any of that — it is a worse imitation of radio
  than the thing it is imitating.

**Apple Music is the platform that permits this.** MusicKit supports full-track
playback for authenticated subscribers with no user cap and no quota-extension
gate — unlike Spotify's Web Playback SDK, which rides the capped Web API.

**The honest engineering risk, and it is the main unknown:** DRM playback in a
browser is finicky (Firefox and Safari play full tracks; Chromium-based browsers
can fall back to 30-second previews if not correctly configured), and
**interleaving DRM-protected music with your own audio in a single seamless
queue is not a documented, supported operation.** Expect to run two players and
cross-fade between them; a native iOS app gives materially more control than the
web. **Prototype exactly this before building anything on top of it** — the
whole product rests on that one interaction working smoothly.

### The appreciation splices are nearly unblocked — ship these first

"Understanding jazz", "a lifetime of Nick Cave" — no per-user generation, no
private data, no volume problem, and genuinely shareable. This variant can go
out on **Spotify today with zero Web API access**: publish one public show
through Spotify for Creators, build the companion playlist by hand (or with the
5-user API), and share a link.

It is also the better *product* bet: it is the part someone can send to a friend.

### The remaining risks — real, all manageable

| Risk | Why it matters | Response |
|---|---|---|
| **Voice cloning** | Spotify bans AI impersonation outright; cloning a real person (including a connected family member) is also a consent problem | A synthetic DJ persona with its own name and voice. Never clone a real person, ever — not even with permission, until there is a documented consent flow |
| **Hallucination** | AI commentary asserting false things about real, living artists is a publishing risk once anything is shareable | Ground every factual claim in a cited source; human review before any public/shareable splice ships. Personal splices are lower-stakes but not zero |
| **Consent for relayed listening** | "Your dad has been listening to…" narrates one person's behaviour to another | Explicit opt-in per connection; let people see what has been shared about them and revoke it. Design this *before* the feature, not after a complaint |
| **Music inside episodes** | Any copyrighted clip or music bed turns a licence-free speech file into a licensing problem | Episodes stay **speech-only**. This is what keeps them free to generate and distribute |
| **Per-user generation cost** | LLM + TTS per user per day is a recurring *per-user* cost, unlike most software | Model it before scaling. Cache aggressively; not every segment needs regenerating daily |
| **Spotify already ships a DJ** | The obvious "they already do this" objection | Theirs cannot say what your dad thought about Nick Cave. The social and educational angles are the defensible ground — the tidbits alone are not |

### On acquiring a grandfathered app

Raised as a route to Spotify access. Treat it as a **fragile theory, not a plan**.
Extended access is granted to an entity under developer terms that generally
restrict assignment, and a change of control is exactly the trigger for review —
Spotify can revoke at will and has been revoking aggressively. Buying the
*company* is more plausible than buying the *app*, but the asset you would be
paying for is one that the counterparty cannot actually guarantee. If it is ever
seriously pursued, it is a lawyer's question first. **Do not put it on a
roadmap.**

## 3d. Platform reality for the player architecture (2026-08-05)

Once §3c settled that DailySplice must be **its own player**, the platform
question stopped being "which one has a nice API" and became "which one lets a
third party play music inside its own app." That is a much narrower list.

| Platform | Own-player playback | Playlist writing | Verdict |
|---|---|---|---|
| **Apple Music** | **Yes** — MusicKit plays full tracks for authenticated subscribers | Yes | **The only platform where the full product works** |
| **YouTube / YouTube Music** | **No — prohibited** | Yes (Data API v3) | Playlist model only. Cannot do the radio experience |
| **Spotify** | Web Playback SDK, but it rides the capped Web API | Yes, ≤5 users | Personal use only |
| **TIDAL** | Not established — worth checking if it ever matters | Yes | Unknown |

### YouTube cannot host this product — its terms forbid the architecture

Checked the YouTube API Services Terms and Developer Policies directly. Three
clauses each independently kill the player design:

- *"Your API Client will not access YouTube audiovisual content by any means
  other than use of a YouTube player or other video player expressly authorized
  by YouTube."* — no custom player, full stop.
- *"You must not apply alternate audio tracks to videos or promote separately the
  audio or video components."* — interleaving a DJ track with YouTube audio is
  close to a textbook description of this.
- No **background play**: content may not play when the app window is closed or
  minimized. A listening app that stops when the phone is pocketed is not a
  listening app.

So YouTube is available for the **playlist** model (write a playlist, the user
plays it in YouTube Music) but not for the radio model. Do not plan on it as a
second home for the player.

**Consequence: Apple Music is a single point of failure for the full product.**
That is worth stating plainly, because it is the same class of risk that Spotify
just demonstrated. The mitigation is the `MusicProvider` boundary (agents.md) —
it does not remove the risk, it just keeps the cost of a forced move bounded.

### On "grow to 250k on other platforms, then qualify for Spotify"

The proposed arc — build on Apple/YouTube, market through Spotify podcasts, grow
to 250k MAU, then apply for Spotify API access — is coherent, and the first two
thirds are right. The destination is the weak part, for four reasons:

1. **At 250,000 monthly active users, the Spotify gate no longer matters.** That
   is a genuinely large consumer product — a real business with real revenue. The
   requirement is self-negating: it only opens once you no longer need it.
2. **Clearing the bar makes you eligible, not approved.** The stated criteria are
   a *minimum*, layered under a discretionary judgment about "established,
   scalable, and impactful use cases that help drive our platform strategy
   forward." Plus a registered entity, key-market availability, and a six-week
   review.
3. **Success makes approval *less* likely, not more.** A player that replaces the
   listening experience and drives listening to Apple Music is, from Spotify's
   side, a competitor. The stronger DailySplice gets on another platform, the
   worse that application reads. This is an inverse relationship and it is the
   most under-appreciated part of the plan.
4. **The policy has moved three times in eighteen months, always tighter.**
   Planning a multi-year route to a door that has only ever been closing is
   planning against a moving target.

**The reframing that keeps the strategy and drops the trap:** build on Apple
Music because it is the platform that permits the product — not as a detour on
the way back to Spotify. Use Spotify podcasts as a distribution channel because
the reach is free and ungated. Treat Web API access as a lottery ticket that
would be pleasant to win and that **nothing depends on**.

**What is genuinely strong here, and worth naming:** the appreciation splices are
that rare case where the marketing artifact *is* a working sample of the product.
Someone who listens to "understanding jazz" on Spotify has already had the
experience the app sells. Most content marketing only describes the product; this
performs it. That is the real asset in the plan — more than the Spotify endgame.

Realistic caveat: podcast discovery on Spotify is hard (roughly 7 million titles).
It is a channel, not a growth engine, and Apple Music's subscriber base is a
fraction of Spotify's — so the addressable market is smaller than the raw
streaming-market numbers suggest. Neither invalidates the plan; both should be in
the model before any revenue forecast is.

## 4. F2+ — sequenced after F1

Sketch only; ordering is the point, not the detail.

- **F2 — Spotify link. PARTIALLY SHIPPED (v0.2.0).** Per-user OAuth
  (authorization code + `state`), encrypted token storage, refresh-on-demand,
  connect/disconnect UI — all built. **Remaining:** rian registers the Spotify
  app and fills the two placeholders in `.app.env`, then proving one
  hand-assembled playlist can be written to a real account. Read §3b first;
  reading the user's *saved library* is no longer possible in Development Mode.
- **F3 — episode substrate.** Whatever §3.1 decides: ingest, store, or reference.
- **F4 — the splice engine.** A pure, testable function: inputs (candidate
  tracks, candidate episodes, rules, date) → an ordered playlist. It must be
  testable with no network — that is what makes the rules arguable.
- **F5 — the daily job.** Scheduling, idempotency (running twice on one day must
  not double-write), and failure handling when Spotify is down.
- **F6 — invitations UI.** Today invites are seeded in code and rows in a table;
  a `superadmin` screen replaces that.

## 5. Out of scope (binding until F4 ships)

Point at this when scope arrives. Every item is plausible and all of them
together are why this project has restarted before.

- Self-serve signup, billing, teams.
- Recommendation ML / taste modelling. The splice rules are deterministic first.
- Apple Music, YouTube Music, or any second provider.
- In-app audio playback or a player UI.
- Native or PWA packaging.
- Podcast *recording* tooling in-app.
- Analytics dashboards.

## 6. Security decisions taken up front

Recorded now so F2 does not have to invent them under pressure.

- **BW client secret** lives only in `.bw-auth.env`, written by the gateway
  (`app-client-register --secret-out`), mode 600, never printed. Rotation:
  `srv-gw app-client-rotate-secret --client-id dailysplice`.
- **App session secret** and the **Postgres password** live in `.app.env`, mode
  660 group `dailysplice-dev` — the gateway runs the deploy and must read it, so
  `600 rian:rian` would fail (`react.md`). Never `${}`-interpolated in compose.
- **Spotify refresh tokens are user credentials for a third-party account.**
  **Implemented in v0.2.0:** Fernet-encrypted at rest with `TOKEN_ENCRYPTION_KEY`
  from `.app.env`, never logged, never returned by any API route, deleted when a
  user unlinks. `app/services/crypto.py` **fails closed** — with no key it
  raises rather than storing plaintext, and a test pins that.
  **Key rotation:** there is no re-encryption path. Changing the key makes
  stored tokens undecryptable, which surfaces to users as "reconnect Spotify".
  That is the accepted behaviour, not an oversight — with at most five users it
  is cheaper than a migration harness. Revisit if the user count ever grows.
- **`show_dialog=true` on every authorize.** Without it a user cannot choose
  *which* Spotify account to connect; they are silently re-linked to whichever
  one their browser is signed in to.
- **The host must never be gated** (`agents.md`). Hiding pre-launch is
  `--lan-only true`.
- **Default-deny stays default-deny.** No `/api/*` exemption, ever — that exact
  inversion is a documented repeat failure on this server.

## 7. Documented deviations

1. **`docker-compose.yml`, `.env`, `.app.env`, `.bw-auth.env` sit at the
   workspace root, not beside the instance.** `srv-gw deploy` requires the
   compose file at the project root. Retired by v8 T3.1 (instance-granular
   registration); the folder shape is already correct, so T3.1 is a
   re-registration rather than a restructure.
