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.
This commit is contained in:
russell@unturf.com 2026-05-09 16:51:24 -04:00
parent b3d9b2b39c
commit 2659ebdcbe
No known key found for this signature in database
15 changed files with 458 additions and 21 deletions

View file

@ -234,6 +234,7 @@ mps_page_session (raw rows)
| [MPS-19](tickets/mps-19.md) | BitTorrent / Magnet Link — Diagnose & Fix Distribution | Open (Broken in prod) |
| [MPS-20](tickets/mps-20.md) | Auction House Mode (eBay-style Bidding) | Proposed |
| [MPS-21](tickets/mps-21.md) | Make-an-Offer Mode | Proposed |
| [MPS-22](tickets/mps-22.md) | Kill-Switch Feature Flags — Karaoke + Torrent Off by Default | Complete |
## Related Docs

140
docs/tickets/mps-22.md Normal file
View file

@ -0,0 +1,140 @@
# 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