make_post_sell/docs/tickets/mps-22.md
russell@unturf.com 2659ebdcbe
MPS-22: kill-switch feature flags for karaoke + torrent
Karaoke (MPS-18) and torrent (MPS-19) are broken in production. Adding
two global feature flags off by default so neither feature surfaces in
UI or accepts route traffic until they're fixed.

Pattern mirrors app.features.popout_player.enabled — reified request
properties (request.karaoke_enabled, request.torrent_enabled) read from
ini settings. Templates wrap UI in {% if %}, views return HTTPNotFound
on form sections + routes, view contexts blank out feature-specific
keys when flag off so SPA navigation does not try to render them.

test.ini sets both flags True so existing feature tests keep working.
TestKillSwitches builds a fresh app with both False and verifies the
off path: form_section POSTs return 404, settings page omits sections,
karaoke route 404s, landing page omits karaoke marketing copy.

GET /s/{shop_id}/torrent-backfill-status is shadowed by an earlier
shop_slug catch-all route in production — pre-existing routing defect
that MPS-19 needs to fix when it lands.
2026-05-09 16:51:24 -04:00

6.7 KiB

MPS-22: Kill-Switch Feature Flags — Karaoke + Torrent Off by Default

Status

TO IMPLEMENT. Karaoke (MPS-18) and torrent (MPS-19) are broken in production. Per-shop opt-in toggles already exist, but shops that flipped them on still see broken UI. Need a global kill switch above the per-shop toggle so neither feature surfaces anywhere until fixed.

Problem

  • Karaoke is gated only by shop.unsandbox_public_key + secret_key. A shop with creds set sees broken karaoke UI on every product page.
  • Torrent is gated only by shop.torrent_enabled. Toggle on → broken magnet button + dead backfill UI.

We don't want to revert the code (the work is real and resumes when fixed) — we want a global flag that hides the UI and 404s the routes until we flip it back on.

Proposal

Mirror the existing app.features.popout_player.enabled pattern exactly:

  1. Two new ini settings, both default False:
    • app.features.karaoke.enabled = ${MPS_FEATURES_KARAOKE_ENABLED:-False}
    • app.features.torrent.enabled = ${MPS_FEATURES_TORRENT_ENABLED:-False}
  2. Two reified request properties in request_methods.py:
    • request.karaoke_enabled
    • request.torrent_enabled
  3. Template guards wrap every UI surface in {% if request.X_enabled %}
  4. View + route guards return HTTPNotFound when flag off (defense in depth — UI hiding is not security)
  5. Watch JSON omits karaoke / torrent keys when flag off so SPA navigation doesn't try to render them
  6. Backfill scripts skip work when flag off

Why off-by-default vs True like popout_player?

popout_player defaults True because it works. Karaoke and torrent default False because they don't. When MPS-18 and MPS-19 land, flip the dev default to True and add MPS_FEATURES_KARAOKE_ENABLED=True to ~/git/foxhop-pillar/uwsgi/makepostsell/init.sls for prod.

UI Surfaces to Hide

Karaoke

File Surface
templates/shop_settings.j2 Unsandbox API keys section + backfill button
templates/content.j2 Karaoke player toggle + URLs
templates/player.j2 Karaoke audio source switch
templates/snippets/related_content.j2 Karaoke indicator on related items
templates/home.j2 Any karaoke discovery / promo
static/js/watch.js Karaoke toggle (JSON keys absent → no-op naturally)

Torrent

File Surface
templates/shop_settings.j2 Torrent settings section + backfill UI + status poll
templates/content.j2 Magnet link button
templates/product_edit.j2 Per-product opt-in toggle

Routes to 404 When Off

Route View
POST /karaoke/{product_id} views/watch.py:karaoke_process
GET /s/{shop_id}/torrent-backfill-status views/shop.py
Settings form_section=backfill-karaoke views/shop.py:1019-1023
Settings form_section=unsandbox-settings views/shop.py (creds save)
Settings form_section=torrent-settings views/shop.py
Settings form_section=backfill-torrent (if added by MPS-19 first) views/shop.py
Karaoke trigger in views/product.py:464-475 gate on request.karaoke_enabled

Files

File Change
data/development.ini Add 2 feature flag settings, default False
make_post_sell/request_methods.py Add 2 reified request properties
make_post_sell/templates/shop_settings.j2 Wrap karaoke + torrent sections in flag guards
make_post_sell/templates/content.j2 Wrap karaoke + magnet UI
make_post_sell/templates/player.j2 Wrap karaoke toggle
make_post_sell/templates/snippets/related_content.j2 Wrap karaoke indicator
make_post_sell/templates/home.j2 Wrap any karaoke promo
make_post_sell/templates/product_edit.j2 Wrap torrent opt-in toggle
make_post_sell/views/watch.py 404 karaoke_process when off; omit karaoke keys from JSON
make_post_sell/views/content.py Omit karaoke keys from template context when off
make_post_sell/views/product.py Skip karaoke spawn when off
make_post_sell/views/shop.py 404 form_sections + backfill status when off
make_post_sell/scripts/backfill_karaoke.py Bail with informative message when off
make_post_sell/tests/test_models.py Request property unit tests
make_post_sell/tests/test_integration.py Form section refusal when off
make_post_sell/tests/test_functional.py UI hidden / routes 404 when off
CLAUDE.md Document kill-switch pattern + current flag state
docs/architecture.md Add MPS-22 to ticket index

Tests

Unit (test_models.py)

  • request.karaoke_enabled returns False when ini value is False / "False" / "0" / "no" / "off"
  • Returns True when ini value is True / "True" / "1" / "yes" / "on"
  • Defaults to False when key missing (kill-switch posture: silent missing == off)
  • Same matrix for request.torrent_enabled

Integration (test_integration.py)

  • Settings POST form_section=unsandbox-settings raises HTTPNotFound when karaoke off
  • Settings POST form_section=backfill-karaoke raises HTTPNotFound when karaoke off
  • Settings POST form_section=torrent-settings raises HTTPNotFound when torrent off
  • When flag on, same POSTs succeed (existing behavior)

Functional (test_functional.py)

  • Shop settings page response does not contain strings "Unsandbox", "karaoke", "Backfill Vocal", "torrent", "magnet" when both flags off
  • Shop settings page does contain them when both flags on
  • POST /karaoke/{product_id} → 404 when off, 200 when on (with creds)
  • GET /s/{shop_id}/torrent-backfill-status → 404 when off
  • Product page response does not include magnet button or karaoke toggle when off
  • Watch JSON response does not include karaoke_* or torrent_* keys when respective flag off

Go-to-Market

Surface Action
~/git/foxhop-pillar/uwsgi/makepostsell/init.sls (later) add MPS_FEATURES_KARAOKE_ENABLED and MPS_FEATURES_TORRENT_ENABLED env vars when ready to flip on
docs/architecture.md Note MPS-22 + reference both flags in feature toggle matrix
CLAUDE.md Add "Feature Kill Switches" section listing current flags

No marketing portal change — these are internal flags. Only flip MPS-18 / MPS-19 GTM when those tickets ship.

Verification

  1. source vars.sh && make test — all pass
  2. Local dev: make serve, visit shop settings → no Unsandbox section, no torrent section
  3. Visit a product page → no magnet button, no karaoke toggle
  4. curl -X POST /karaoke/{product_id} → 404
  5. Flip both env vars to True, restart, verify all UI returns
  6. Flip back to False, verify clean hide again
  7. Push → CI green → deploy → bump GIT_HASH
  8. Prod check: visit my.makepostsell.com shop settings, confirm sections gone