Merge branch 'worktree-supervisor-flags'

Python supervisor + DB-backed feature flags

- supervisor.py replaces supervisor.sh (topological wave scheduling, croniter)
- workflows.toml workflow registry (5 extractors, cron presets, depends_on)
- proxy.py round-robin + sticky proxy rotation via PROXY_URLS
- Feature flags: migration 0019, is_flag_enabled(), feature_gate() decorator
- Admin /admin/flags UI with toggle (admin-only)
- lead_unlock gate on unlock_lead route
- 59 new tests (test_supervisor.py + test_feature_flags.py)
- Fix is_flag_enabled bug (fetch_one instead of execute_fetchone)

# Conflicts:
#	CHANGELOG.md
#	web/pyproject.toml
This commit is contained in:
Deeman
2026-02-23 15:29:43 +01:00
29 changed files with 1923 additions and 163 deletions

View File

@@ -251,13 +251,16 @@ class TestAuthRoutes:
@pytest.mark.asyncio
async def test_normal_signup_when_waitlist_disabled(self, client, db):
"""Normal signup flow when WAITLIST_MODE is false."""
with patch.object(core.config, "WAITLIST_MODE", False):
response = await client.get("/auth/signup")
assert response.status_code == 200
html = await response.get_data(as_text=True)
# Should see normal signup form, not waitlist form
assert "Create Free Account" in html or "Sign Up" in html
"""Normal signup flow when payments flag is enabled (WAITLIST_MODE replaced by feature flag)."""
await db.execute(
"INSERT OR REPLACE INTO feature_flags (name, enabled, description) VALUES ('payments', 1, '')"
)
await db.commit()
response = await client.get("/auth/signup")
assert response.status_code == 200
html = await response.get_data(as_text=True)
# Should see normal signup form, not waitlist form
assert "Create Free Account" in html or "Sign Up" in html
@pytest.mark.asyncio
async def test_shows_waitlist_form_when_enabled(self, client, db):
@@ -713,13 +716,16 @@ class TestWaitlistGateDecorator:
"""Test waitlist_gate decorator via integration tests."""
@pytest.mark.asyncio
async def test_passes_through_when_waitlist_disabled(self, client):
"""Decorator passes through to normal flow when WAITLIST_MODE=false."""
with patch.object(core.config, "WAITLIST_MODE", False):
response = await client.get("/auth/signup")
html = await response.get_data(as_text=True)
# Should see normal signup, not waitlist
assert "waitlist" not in html.lower() or "create" in html.lower()
async def test_passes_through_when_waitlist_disabled(self, client, db):
"""feature_gate passes through to normal form when payments flag is enabled."""
await db.execute(
"INSERT OR REPLACE INTO feature_flags (name, enabled, description) VALUES ('payments', 1, '')"
)
await db.commit()
response = await client.get("/auth/signup")
html = await response.get_data(as_text=True)
# Should see normal signup, not waitlist
assert "waitlist" not in html.lower() or "create" in html.lower()
@pytest.mark.asyncio
async def test_intercepts_get_when_waitlist_enabled(self, client):
@@ -940,21 +946,21 @@ class TestIntegration:
@pytest.mark.asyncio
async def test_toggle_off_reverts_to_normal_signup(self, client, db):
"""Setting WAITLIST_MODE=false reverts to normal signup flow."""
# First, enable waitlist mode
with patch.object(core.config, "WAITLIST_MODE", True):
response = await client.get("/auth/signup")
html = await response.get_data(as_text=True)
assert "waitlist" in html.lower()
"""Disabling payments flag shows waitlist; enabling it shows normal signup."""
# payments=0 (default) → shows waitlist page
response = await client.get("/auth/signup")
html = await response.get_data(as_text=True)
assert "waitlist" in html.lower()
# Then, disable waitlist mode
with patch.object(core.config, "WAITLIST_MODE", False):
response = await client.get("/auth/signup")
html = await response.get_data(as_text=True)
# Should see normal signup, not waitlist
assert "create" in html.lower() or "sign up" in html.lower()
# Should NOT see waitlist messaging
assert "join the waitlist" not in html.lower()
# Enable payments flag → shows normal signup
await db.execute(
"INSERT OR REPLACE INTO feature_flags (name, enabled, description) VALUES ('payments', 1, '')"
)
await db.commit()
response = await client.get("/auth/signup")
html = await response.get_data(as_text=True)
assert "create" in html.lower() or "sign up" in html.lower()
assert "join the waitlist" not in html.lower()
@pytest.mark.asyncio
async def test_same_email_different_intents_both_captured(self, client, db):