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.
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:
- 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}
- Two reified request properties in
request_methods.py:request.karaoke_enabledrequest.torrent_enabled
- Template guards wrap every UI surface in
{% if request.X_enabled %} - View + route guards return
HTTPNotFoundwhen flag off (defense in depth — UI hiding is not security) - Watch JSON omits karaoke / torrent keys when flag off so SPA navigation doesn't try to render them
- 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_enabledreturns False when ini value isFalse/"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-settingsraises HTTPNotFound when karaoke off - Settings POST
form_section=backfill-karaokeraises HTTPNotFound when karaoke off - Settings POST
form_section=torrent-settingsraises 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_*ortorrent_*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
source vars.sh && make test— all pass- Local dev:
make serve, visit shop settings → no Unsandbox section, no torrent section - Visit a product page → no magnet button, no karaoke toggle
curl -X POST /karaoke/{product_id}→ 404- Flip both env vars to
True, restart, verify all UI returns - Flip back to
False, verify clean hide again - Push → CI green → deploy → bump GIT_HASH
- Prod check: visit my.makepostsell.com shop settings, confirm sections gone