Monetization · master report — one place

Lissin Monetization — Master Report

Bottom line

The backend entitlement + quota + enforcement engine is complete and hardened; grandfathering covers cohorts and hand-picked users; iOS contains the Phase-4 surface and Pro creator features; and admin contains the operating surfaces. The work is now integrated: backend #522 (+ #523/#524) into dev, admin #168 into dev, and iOS #317 into develop. It remains observe-only / capability-gated / flag-gated until configuration and rollout. On 21 July, the missing offer jump was traced to swipe dismissal bypassing RevenueCat’s callback plus standard-offering fallback masking absent discounts. Fix c6305160 is pushed on fix/offer-ladder-dismiss-pop and still needs merge. Product sign-offs, exact save50/save65 RevenueCat/App Store artifacts, sandbox QA, a new build, and flag ramp remain.

Latest (2026-07-20) — Phase-4 + selective grandfathering shipped, all comments cleared

New this round, on top of the earlier foundation: backend #515 gained grandfather-by-user-IDs (preview + idempotent bulk apply) and a campaigns audit; admin #163 gained a Users-page multi-select → "Grandfather selected (N)" flow and a grandfathering audit page; iOS #311 is the offer-stage rescue ladder (persists & resumes, resets on purchase, commits a stage only after its offering loads); iOS #312 is Phase 4 — gates (transcript / playback-speed / soundscape / explore-time / file size+count), trial banner, usage nudges, and live balances. All new review P1s fixed: #515 require-filters (400 not 500) + past-expiry reject; #311 ladder concurrency (commit-after-load + re-entrancy guard). #312 was rebased onto the fixed ladder — the whole iOS stack builds & 356 tests pass. iOS merge order: #309 → #311 → #312. The pro_sources paywall is now wired by the source-monitoring surface (iOS #315 — see the creator-feature stack below). Enforcement stays OFF.

Also (2026-07-20) — Pro creator-feature stack built (all dark)

Stacked on the billing foundation, nine more PRs now build the Pro "creator" features the plan contract defines — every one observe-only / capability-gated / dark, nothing enforced: creator export — transcript / script / audio (backend #518 on feat/billing-quota-foundation, iOS #313 on feat/billing-phase4; built + tested); advanced transcript tools — search / tap-to-seek / copy (iOS #314; build succeeded Xcode 26.5, 16 tests); source monitoring — a backend foundation (MonitoredSource model + per-user CRUD + sourceMonitoring gate · backend #520, 62 tests) plus the iOS Sources screen (Profile→Settings) that wires the pro_sources paywall — client gate + server-402→.proSources mapping (iOS #315; build succeeded Xcode 26.5, 13/13 tests); DB-driven plan config — runtime resolves the active plan_versions row from Postgres (30s cache, fail-open to code) + a publish/activate API (backend #519) with a full per-tier plan-config editor UI — edit every value, preview + publish a version, version history / rollback (admin #166; tsc + next build clean); and soundscape per-tier cataloging — a tier column + migration + public-API field + admin set-tier endpoint + seed defaults (backend #521) with the tier-assignment UI in studio/soundscapes (admin #167). Deferred / not built: the source-monitoring polling / auto-generation pipeline (only the foundation shipped), and sound-effects cataloging — proposed only, since there is no sound-effects feature in the app to tier (soundscapes are background-music beds). Enforcement stays OFF.

Status at a glance

Backend PR #514

dev · feat/billing-quota-foundation · 17 commits

✅ Contract → ledger → quota → access API → trial

✅ Observe-only metering on all user-initiated paid actions

✅ Enforcement (402) behind per-domain flags + fail-safe

✅ full jest suite 1851/1857 (5 pre-existing on dev, 0 billing)

Backend PR #515

dev · feat/billing-grandfather-cohort · 4 commits

✅ Grandfather by filtered cohort (preview + idempotent apply)

✅ Grandfather by hand-picked user IDs (preview + apply)

✅ Campaigns audit list + per-campaign grant drill-down

✅ 29/29 billing+grandfather tests; overlay-safe, bounded, idempotent

Admin PR #163

dev (admin.dev) · feat/billing-grants-admin · 11 commits

✅ Grant / revoke / history / operator attribution + access contract

✅ Cohort grandfathering UI (builder → preview → apply)

✅ Users-page multi-select → bulk "Grandfather selected (N)"

✅ Grandfathering audit page + nav; tsc + next build pass

iOS PR #309

develop · feat/billing-plan-contract · 9 commits

✅ Decodes expanded /billing/access contract

✅ Fallback plan aligned to Phase-0 numbers

✅ Decodes 402 denial → paywall-placement mapping

✅ base of the iOS stack (#311 + #312 build on it)

iOS PR #311

develop · feat/billing-offer-ladder · 6 commits · stacks on #309

✅ Offer-stage rescue ladder: standard → 50% → 65% downsell

✅ Persists highest stage, resumes on reopen, resets on purchase

✅ Commits a stage only after its RC offering loads (P1 fix)

✅ Re-entrancy-guarded dismissal; 13 OfferLadderTests

iOS PR #312

develop · feat/billing-phase4 · 12 commits · stacks on #311

✅ Gates: transcript / speed / soundscape / explore-time / file size+count

✅ Trial countdown banner + usage nudges (approaching-limit)

✅ Plan & Billing on live per-meter balances (fallback to legacy)

✅ full iOS stack compiles + 356 tests pass (Xcode 26.5)

Backend PR #518

feat/billing-quota-foundation · creator export

✅ Export transcript / script / show-audio (Pro, creatorExport)

✅ Capability-gated, dark by default · built + tested

iOS PR #313

feat/billing-phase4 · creator export UI

✅ Transcript / script / audio export surface (Pro)

✅ Built + tested; gated until entitlement + flags live

iOS PR #314

feat/billing-phase4 · advanced transcript

✅ Search / tap-to-seek / copy behind advancedTranscript

✅ Build succeeded (Xcode 26.5) · 16 tests

Backend PR #520

feat/billing-quota-foundation · source monitoring

MonitoredSource model + per-user CRUD + sourceMonitoring gate

✅ 62 tests · polling / auto-gen deferred

iOS PR #315

feat/billing-phase4 · Sources screen (stacks on #312)

✅ Native Sources screen (Profile→Settings) + pro_sources paywall

✅ Client gate + server-402→.proSources map · Xcode 26.5, 13/13 tests

Backend PR #519

→ DB-driven plan config + publish/activate API

✅ Runtime resolves active plan_versions row from Postgres (30s cache)

✅ Fail-open to code contract · built + tested

Admin PR #166

feat/billing-grandfathering-admin · plan editor

✅ Edit every plan value per tier · preview + publish a version

✅ Version history / rollback · tsc + next build clean

Backend PR #521

dev · soundscape per-tier cataloging

tier column + migration + public-API field + set-tier endpoint

✅ Seed defaults (basic / extended / complete)

Admin PR #167

dev · soundscape tier UI

✅ Tier-assignment UI in studio/soundscapes

✅ Built

AreaState
Backend entitlement + quota + enforcementDONE (dark)
iOS billing contract + 402 handlingDONE #309
iOS offer-stage rescue ladder (50%/65% downsell)DONE #311
iOS Phase-4 gates + trial banner + nudges + live balancesDONE #312
Grandfathering — cohort + hand-picked users + auditDONE #515 (backend) + #163 (admin UI)
Creator export — transcript / script / audio (Pro)DONE (dark) #518 (backend) + #313 (iOS)
Advanced transcript tools — search / tap-to-seek / copy (Pro)DONE (dark) #314 (iOS)
Source monitoring (Pro) — model + CRUD + gate + Sources screen + pro_sourcesDONE (dark) #520 (backend) + #315 (iOS) · polling deferred
DB-driven plan config + plan-config editor UIDONE (dark) #519 (backend) + #166 (admin)
Soundscape per-tier cataloging (tier column + UI)DONE (dark) #521 (backend) + #167 (admin)
Sound-effects catalogingPROPOSED needs an SFX feature to exist first
Product sign-offs (prices, discount duration)PARTIAL core numbers confirmed
App Store + RevenueCat catalog (incl. save50/save65 offerings)NOT STARTED (yours; checklist ready)
Analytics / store QA / % rampNOT STARTED (future)

Review — what I checked & fixed

I did a correctness pass over the whole branch. Findings:

CheckResult
External code-review comments (Greptile P1/P2 + admin security)ALL FIXED#514 ba17c42: quota-account update now optimistic-locked on revision with bounded retry (atomicity P1), snapshot limit includes rollover (P2), rollover-approximation documented (P2). #515 79bb1c8: idempotency matches by key alone (P2), filter-hash normalizes implicit defaults (P2), excludeEmailDomains must start with @/. (P1). #309 9b7b8fc5: per-meter balance decode so one bad meter can't drop all balances (P1), tier-aware trial fallback (P2), fileUploads test coverage (P2). #163: grant mutations gated on billing.manage (2× P1 security, confirmed at HEAD). Build + billing tests green after each.
New-round review comments (grandfather-by-users + offer ladder + Phase 4)ALL FIXED · re-reviews green#515 9164b10: @IsDefined on filters (omitted body → clean 400) + reject past expiresAt (no silently-dead grants). #311 c39b4fb1+065a4584: ladder commits a stage only after its offering loads (peek→load→commit) so a failed load can't strand a user; close button re-entrancy-guarded; and loadPackages(offeringIdentifier:) returns false when busy (never a false "loaded" signal). #312 c32084c4: trial banner refreshes now each minute (no stale "last day" after expiry), nudge-dismiss animates, defensive balance subtracts reserved. #312 rebased onto the fixed ladder; full iOS suite 356/356. Greptile re-reviews on #311 & #312 both COMPLETED / SUCCESS, no open comments.
Enforcement could break a request on an internal read errorFIXED a47b39c — the quota peek in enforce() now fails OPEN (logs, never throws); only a genuine over-limit denies. Covers the trial-phase-without-window edge.
Concurrent same-window account creation could throw P2002FIXED a47b39censureAccount re-reads the row on a race.
Metering must never break a user actionOK — all observeUsage/observe calls are fire-and-forget + try/caught; non-fatal by construction.
Half-deployed / auth-fail can't downgrade payersOK — new access fields are additive/optional; iOS treats 404/5xx as "unavailable → keep local", 401/400 never silently grant.
Webhook idempotency + out-of-order + transfer + expiryOK — dedupe by provider event id, stale-event ordering guard, TRANSFER stand-down, read-time expiry; all unit-tested.
Enforcement default stateOK — every BILLING_ENFORCE* flag defaults off; verified per-domain + explicit-false override by test.
Pre-existing failures (verified)NOT MINE — the 5 backend failures (posthog-credential, content.getLibrary ×3, generation.duration) fail identically on clean dev; #514 touches none of those files. Zero billing/quota/grandfather failures.

Known limitations (by design, documented): enforcement uses a read-only peek (small concurrency window vs a strict atomic reserve — the reserve/consume path exists & is tested, ready to wire); enforce() + observeUsage each read the access snapshot, so a generation does two snapshot reads in observe mode (fine on dev; optimizable). Scheduler recurring metering was deferred (see "What's left").

What's done — the billing foundation (59 commits across six PRs)

Backend PR #514 (17 commits)

ba17c42  fix: optimistic-lock reserve + rollover-aware limit (review)
d203b21  recurring=shows/week, one-time=episodes/week (revised numbers)
a47b39c  fail-safe hardening (enforce peek fails open; account race re-read)
4e1e491  document BILLING_ENFORCE[_DOMAIN] flags in .env.example
333c46b  pre-flight enforcement on generation + document upload
9fe7dc2  enforcement engine behind per-domain flags (402 denials)
4804bf4  observe-only metering for Explore playback + SSE creates
2de1f2e  wire generation + document upload into observe-only metering
fda8643  observe-only metering via QuotaService.observe
17fed30  project the trial phase from the RevenueCat webhook
c9caf70  expand GET /billing/access with phase, balances, trial
17b250a  access phase + trial fields on billing_access
f546ffc  quota service — balances, reserve/finalize/release
8a6e08b  UTC-anchored usage-window math for quota meters
f4adc3f  usage account + immutable usage ledger schema
8edc016  plan_versions table seeded from the plan contract
2c8591e  versioned credit-based plan contract (source of truth)

Backend PR #515 (4 commits)

9164b10  fix: require filters (400 not 500) + reject past expiry (review)
87c7548  grandfather a hand-picked user-id selection + campaign audit
79bb1c8  fix: key-only idempotency, normalized filter hash, @-anchored domain filter (review)
537f62b  cohort grandfathering — preview + idempotent bulk apply

iOS PR #309 (9 commits)

9b7b8fc5  fix: per-meter balance decode, tier-aware trial fallback, cover fileUploads (review)
75af88fe  fix: per-placement paywall sheet detents
b8253838  test: paywall placement screenshot QA harness
bf61e690  chore: bump build to 126 for the paywall-placement Beta TestFlight
18a8530e  sync fallback one-time limits to weekly contract (2/4/10/50)
a5a174e3  react to the backend's structured 402 billing-denial
7dd7e34b  tests: expanded /billing/access contract decoding
d8d93c06  align local fallback plan to credit-based Phase-0 contract
772daf4e  decode expanded /billing/access contract; extend PlanLimits

iOS PR #311 — offer-stage rescue ladder (6 commits · stacks on #309)

065a4584  fix: don't signal a loaded offering while busy (review)
c39b4fb1  fix: commit ladder stage only after offering load + guard re-entrant dismiss (review)
c32b2a4e  test: offer-ladder progression + stage screenshots
2d8b0648  escalate paywall dismissal into the discount downsell
bfbb5112  load a specific RevenueCat offering per ladder stage
ac02f36f  offer-stage rescue ladder model + coordinator

iOS PR #312 — Phase 4 (12 commits · stacks on #311)

c32084c4  fix: live trial clock + nudge animation + reserved-aware balance (review)
c204dbd4  test: trial/nudge/balances logic
d920a41e  drive Plan & Billing meters from live balances
1efaea0f  usage nudges near meter limits
a184aec9  trial countdown banner on home
44b3060d  test: entitlement-gate decision logic
d752e333  enforce per-file size + upload-count limits on attach
6d734ef2  enforce explore listening-time via the metered balance
19102972  gate premium soundscapes behind entitlement
e52414b2  enforce playback-speed ceiling on the default-speed slider
ed6d9b13  gate transcript + enforce playback-speed ceiling in the player
67da7bcd  entitlement-gate helper + meter-aware explore capability

Admin PR #163 (11 commits)

f9a90fa  grandfathering campaigns audit page + nav link
5f92d3f  user multi-select + bulk grandfather on the users list
c7273b0  server actions for hand-picked grandfathering + audit
f32591f  grandfathering admin page with cohort builder + two-click apply
65b2638  grandfather cohort preview + apply server actions
5bbae77  surface expanded access contract in billing panel (phase/trial/balances)
ad83886  seed grant-form defaults on mount, not during render
75a57e3  correct grant status for not-yet-started grants
6485987  attribute grants to the signed-in operator
82ccee4  billing.manage permission for grant mutations; versioned refreshes
7ae2cd4  admin surface for complimentary grants and access snapshots

New Pro creator-feature stack (nine PRs · stacked on the foundation)

Built this round on top of the billing foundation — all observe-only / capability-gated / dark. Commit-by-commit detail lives on each PR; at a glance:

PRWhat it buildsBase · state
Backend #518Creator export — transcript / script / show-audio behind creatorExport (Pro)feat/billing-quota-foundation · built + tested
iOS #313Creator export surface (transcript / script / audio)feat/billing-phase4 · built + tested
iOS #314Advanced transcript tools — search / tap-to-seek / copy behind advancedTranscript (Pro)feat/billing-phase4 · build succeeded (Xcode 26.5), 16 tests
Backend #520Source-monitoring foundation — MonitoredSource model + per-user CRUD + sourceMonitoring gate (polling / auto-gen deferred)feat/billing-quota-foundation · 62 tests
iOS #315Source-monitoring surface — native Sources screen (Profile→Settings) + pro_sources paywall (client gate + server-402→.proSources mapping)feat/billing-phase4 (stacks on #312) · build succeeded (Xcode 26.5), 13/13 tests
Backend #519DB-driven plan config — runtime resolves the active plan_versions row from Postgres (30s cache, fail-open to code) + publish/activate APIbuilt + tested
Admin #166Plan-config editor UI — edit every plan value per tier, preview + publish a version, version history / rollbackfeat/billing-grandfathering-admin · tsc + next build clean
Backend #521Soundscape per-tier cataloging — tier column + migration + public-API field + admin set-tier endpoint + seed defaultsdev · built
Admin #167Soundscape tier-assignment UI in studio/soundscapesdev · built

Not built: the source-monitoring polling / auto-generation pipeline (only the foundation shipped) and sound-effects cataloging — proposed only, since no sound-effects feature exists in the app to tier (soundscapes are background-music beds).

The full pipeline now exists: plan contract → plan_versions (dashboard-editable) → usage ledger → window math → quota service → expanded /billing/access → trial projection → observe-only metering everywhere users act → flag-gated 402 enforcement → iOS decode + placement mapping. Data model added: plan_versions, usage_accounts, usage_ledger, extended billing_access (3 migrations).

Live vs observe-only vs dark

CapabilityState
Access snapshot, trial phase, balances, plan versionslive & additive
Usage metering (generation, upload, Explore playback, SSE)observe-only records + logs would-deny, never blocks
Enforcement / 402 denials (generation, files, capabilities)dark wired, flag-gated, off by default
iOS 402 decode + placement mappingdormant activates when flags flip
iOS offer ladder / gates / trial banner / nudges / live balancesdormant present but gated on entitlement/trial/balance data; harmless until data + flags are live
Creator export · advanced transcript · source-monitoring foundation + Sources screen · soundscape catalogingdark built, capability-gated, off until entitlement + flags live
DB-driven plan config (runtime resolves active plan_versions)live & additive Postgres-backed, 30s cache, fail-open to the code contract

What's left

Non-code (mostly — the real blockers) Product / Store / Ops · You

  1. Product sign-offs — prices, 50/65 discount duration, a few rollover caps, cohort/permanent-access policy, nudge caps.
  2. App Store Connect + RevenueCat catalog — trial intro offer, exact save50 + save65 rescue offerings, placements, paywalls, prod webhook, promo/win-back. Missing discount offerings are skipped; standard prices are never presented as a discount.
  3. QA / store test matrix → sandbox → TestFlight → cohort → % ramp.
  4. Analytics instrumentation + dashboards.

Code (future phases — nice-to-have, not blocking) Backend / iOS / Admin

Your action items

DoWhy / how
Review & merge the PRsBilling foundation (6): backend #514 + #515 → dev; admin #163 → dev; iOS in stack order #309 → #311 → #312develop. Creator-feature stack (9): backend #518 + #520 (on feat/billing-quota-foundation) + #519, admin #166 (on feat/billing-grandfathering-admin), iOS #313 + #314 + #315 (on feat/billing-phase4), and #521 + #167 → dev. All additive / observe-only / capability-gated / dark — safe. Merge backend one at a time (no migrate-concurrency guard).
Create the RevenueCat offerings save50 + save65The offer ladder loads these for its 50%/65% downsell. Until they exist the ladder skips that step gracefully (no breakage). Plus the default offering so base paywalls show prices.
Upload a build to TestFlightVia Xcode Organizer → Distribute (needs Xcode 26.5 — default toolchain is 16.4). Test where paywalls + the ladder appear; payments need the RC offerings above.
Apply the 3 migrations on devAuto on dev-deploy when #514 merges, or start Docker + prisma migrate deploy + npm run seed:plan-versions.
Sign off pending product numbersUnblocks the store catalog. Defaults are versioned config — a data edit, not a redeploy.
Run the App Store + RevenueCat setup checklistThe biggest non-code block; gates real purchases. Checklist is turnkey (below).
When ready, flip enforcement per domainBILLING_ENFORCE_CAPABILITIES=true first, verify, then FILES + GENERATION.

The detailed reports (all in lo/)

PRD source: lo/lissin-monetization-plan/ (docs 01–08). Code — billing foundation: PRs #514 + #515 (backend→dev), #309 + #311 + #312 (iOS→develop, stacked), #163 (admin→dev). Creator-feature stack: #518 + #519 + #520 + #521 (backend), #313 + #314 + #315 (iOS), #166 + #167 (admin). This master report is the single entry point; the reports above go deeper on each dimension.