# Feature Flags + Current Limitations

## Flag matrix (canonical source: `.env`)

| Env var | Config key | Default | Production today | Effect when ON |
|---|---|---|---|---|
| `MARKET_BOOTSTRAP_ENABLED` | `acquisition.market_bootstrap.enabled` | false | **false** | Category page hits fire `MarketBootstrapDispatcher`; spends real Google Places + DataForSEO credits |
| `MARKET_BOOTSTRAP_DEFAULT_RADIUS` | `acquisition.market_bootstrap.default_radius_miles` | 50 | 50 | Provider-query radius around the user's geo cell |
| `MARKET_BOOTSTRAP_LEADS_PER_CATEGORY` | `acquisition.market_bootstrap.leads_per_category` | 30 | 30 | Cap per bootstrap |
| `MARKET_BOOTSTRAP_COOLDOWN_DAYS` | `acquisition.market_bootstrap.cooldown_days` | 30 | 30 | Time before the same cell can re-fire |
| `MARKET_BOOTSTRAP_DAILY_LIMIT_PER_TENANT` | `acquisition.market_bootstrap.daily_limit_per_tenant` | 50 | 50 | Daily ceiling on bootstraps started |
| `MARKET_BOOTSTRAP_GEOHASH_PRECISION` | `acquisition.market_bootstrap.geohash_precision` | 4 | 4 | ~20km cells |
| `ENRICHMENT_AUTO_AFTER_BOOTSTRAP` | `acquisition.enrichment.auto_after_bootstrap` | false | **true** | Auto-queues `EnrichBatchLeadsJob` after every bootstrap |
| `ENRICHMENT_JOB_QUEUE` | `acquisition.enrichment.job_queue` | default | default | Queue name |
| `ENRICHMENT_LEADS_PER_RUN` | `acquisition.enrichment.leads_per_run` | 30 | 30 | Per-job cap |
| `OUTREACH_AUTO_INVITE_AFTER_VERIFY` | `acquisition.outreach.auto_invite_after_verify` | false | **false** | Connector 2 — not built; flag is reserved |
| `OUTREACH_UPGRADE_NUDGE_AFTER_CLAIM` | `acquisition.outreach.upgrade_nudge_after_claim` | false | **false** | Connector 3 — not built; flag is reserved |
| `OUTREACH_DAILY_INVITE_CAP_PER_TENANT` | `acquisition.outreach.daily_invite_cap_per_tenant` | 200 | 200 | Reserved for Connector 2 |
| `SHOW_PUBLIC_CONTACT_ASSET_ICONS` | `searchmercials.directory.show_public_contact_asset_icons` | false | **true** | Public category cards render envelope/phone/globe icons |
| `DEMAND_ACQUISITION_ENABLED` | `acquisition.demand_acquisition.enabled` | false | false | Phase 9 demand-triggered acquisition (separate from Phase B market bootstrap) |
| `STRIPE_KEY/SECRET/WEBHOOK_SECRET` (live) | (cashier) | unset | **unset** | Live keys not configured; test keys only |
| `STRIPE_KEY_TEST/SECRET_TEST/WEBHOOK_SECRET_TEST` | — | unset | **set** | Test mode active |
| `SM_MOCK_PAYMENTS` | `searchmercials.billing.mock_payments` | env-derived | env-dependent | Hides simulate-pay button in prod |
| `GOOGLE_PLACES_API_KEY` | `services.google_places.api_key` | unset | **set** | Required for primary discovery provider |
| `MILLION_VERIFIER_API_KEY` | `services.millionverifier.api_key` | unset | **set** | Required for verification |
| `DATAFORSEO_LOGIN/API_KEY` | (used directly) | unset | **set** | DataForSEO fallback provider |

## Known limitations (what's NOT working today)

### Outreach is off

**Effect**: 498 verified-deliverable emails on tenant 2 are never contacted.
The Activation Funnel page reads `0 invited / 0 opened / 0 clicked / 0 claimed`
across the board.

**Why**: Connector 2 was scoped in the prior audit but never built. The
listener hook from `LeadQualificationRunner::runOne` to a new
`SendClaimInvitationJob` doesn't exist.

**To fix**: build Connector 2 (~3 files, ~100 lines, daily-cap config knob).

### Wave 1 attribution covers new cohorts only

**Effect**: 0 paid subscriptions on tenant 2 are attributable to acquisition
spend, despite 3 paying vendors existing.

**Why**: The `sm_vendor_leads.market_bootstrap_id` and
`sm_vendors.acquisition_lead_id` FK columns were added on 2026-04-29
(commit `2bf1f67`). All existing vendors and bootstraps predate that.

**To fix**: nothing required. Future cohorts will populate. Legacy stays
honestly disconnected — false attribution would be worse than honest gap.

### Google Places API returns 403

**Effect**: every market bootstrap currently falls through to DataForSEO,
which is materially more expensive per call.

**Why**: "Places API (New)" is not enabled in the Google Cloud Console
for project id `129529621869`.

**To fix**: out-of-band Google Cloud Console step. Visit
`https://console.developers.google.com/apis/api/places.googleapis.com/overview?project=129529621869`,
enable the API, wait ~2 min for propagation. No code change.

### Stripe live keys unset

**Effect**: cannot collect real revenue. Webhook chain has fired 21+ times
in test mode (verified by `sm_payment_gateway_events`); production charges
would need the live keys.

**Why**: live keys never set in `.env`. The 2026-04-27 go-live did not
include the env update.

**To fix**: out-of-band env update. Set `STRIPE_KEY`, `STRIPE_SECRET`,
`STRIPE_WEBHOOK_SECRET` (live values); chown `.env` to www-data; run
`php artisan config:clear`.

### 1,839 legacy leads unverified

**Effect**: ~73% of tenant 2's lead inventory carries a `NULL`
`email_verification_status` because they were created before
auto-enrichment was wired.

**Why**: `EnrichBatchLeadsJob` was added on 2026-04-29; pre-existing
leads bypass it.

**To fix**: one-time backfill run. Either run `php artisan acquisition:qualify`
in commit mode against the unverified subset, or write a small
`acquisition:backfill-verifications` artisan command. Cost ~$10 in
MillionVerifier credits.

### "Backfill" naming collision

**Effect**: two separate sidebar/links surfaces use the word "Backfill":
- `tenant.backfill` page (Phase 9 demand triggers — `sm_demand_triggers`)
- Acquisition Dashboard (Phase B market bootstrap — `sm_market_bootstraps`)

**Why**: organic naming evolution; the same word was used for two related
but distinct concepts.

**To fix**: rename `tenant.backfill` to "Demand triggers" or merge into
the Acquisition Dashboard. UI-only change.

### Pre-claim stub vendor "1,873" framing

**Effect**: dashboards show "1,873 vendors total" without splitting out
that 1,870 are pre-claim stubs from the M1 leak path. Operator forms
inflated mental model.

**Why**: `VendorLeadSeederService` historically pre-materialized vendors
from leads (M1 leak). M2 disconnected the seeder from the invite path
but the historical data remains.

**To fix**: cosmetic UI split — show "Claimed: 3 · Pre-claim inventory:
1,870" instead of "Total: 1,873." No data migration needed.

## Rolling back the auto-enrichment flag

If `ENRICHMENT_AUTO_AFTER_BOOTSTRAP=true` produces unexpected behaviour:

```bash
sed -i '/^ENRICHMENT_AUTO_AFTER_BOOTSTRAP=true$/d' .env
chown www-data:www-data .env && chmod 640 .env
php artisan config:clear
```

Same pattern for any of the flags above.

## Queue worker dependency

`EnrichBatchLeadsJob` runs on the `database` queue connection. As of
2026-04-29 there are 3 workers running (verified):

```
www-data ... queue:work database --queue=media --sleep=3 --tries=2 --max-time=7200 --timeout=3700
www-data ... queue:work database --sleep=3 --tries=3 --max-time=3600 --timeout=600
www-data ... queue:work database --sleep=3 --tries=3 --max-time=3600 --timeout=600
```

If the workers die, enrichment jobs accumulate in `jobs` table without
firing. The flag still reads as ON but no actual work happens. Monitor
via `php artisan queue:failed` or by checking `SELECT COUNT(*) FROM jobs`.
