Skip to Content
RunbooksRunbook: Enable Family Manager for an Org (Feature Flags)

Runbook: Enable Family Manager for an Org (Feature Flags)

Owner: Platform Team Backup owner: On-call engineer Last validated: 2026-06-14 Validation method: Per-household allowlist on a consumer_household dogfood org via scripts/fm_beta_enable.py, verified against the async is_feature_enabled_for_org resolution path Severity trigger: SEV3 (feature inoperable / wrong cohort sees a surface — not data-loss) Customer impact: None on B2B traffic. FM mobile routes return 404 feature_not_enabled for any household not yet allowlisted; the surface stays hidden until granted. Required access: SSH (VPS), the curateme-backend-b2b container (B2B MongoDB) Related services: curateme-backend-b2b, MongoDB (B2B DB org_feature_flag_overrides) Time to complete: ~3 minutes per household


Family Manager is the consumer-household mobile facade built on the platform as a dogfood/reference org. Its surfaces are gated by eight FM_* feature flags (src/config/feature_flags.py). In production every FM_* flag is explicit-OFF — the only ways to arm a household are an env-global FF_FM_*=true (heavy-handed, all orgs) or, the sanctioned path, a per-org override row in org_feature_flag_overrides.

This runbook covers the per-org enable: what each flag does, how the production explicit-OFF + per-org-override resolution actually works, the exact upsert via the sanctioned scripts/fm_beta_enable.py tool, and how to verify a flag resolved ON for the org.


Prerequisites

Before starting, confirm:

  • SSH access to the platform VPSssh curateme@178.105.8.25
  • The target org id (e.g. org_abc123) and that it is a consumer_household tenant — the script refuses any other tenant profile without --force (a B2B workspace must never gain consumer surfaces by a typo’d org id; see scripts/fm_beta_enable.py).
  • The flag work runs against the B2B app DB, so commands execute inside docker exec curateme-backend-b2b (APP_MODULE=src.main_b2b:app, CURATE_API_MODE=b2b).

The eight FM_* flags

Each flag gates one sibling mobile router; flag OFF → the router’s routes return 404 with code="feature_not_enabled" (the hidden surface never confirms it exists). Source of truth: FeatureFlag in src/config/feature_flags.py.

Flag (flag_name)Surface
fm_assistant_profileHousehold assistant profile (Phase 1)
fm_inbound_emailForwarding alias + sender verify + inbound webhook (Phases 2-3)
fm_documentsDocument vault + parsing + previews (Phase 4)
fm_memoryHousehold memory facts + timeline (Phase 6)
fm_expensesConsumer expense/invoice ledger (Phase 5)
fm_askGrounded Ask over docs + memory (Phase 7)
fm_llm_extractionSensitive — third-party LLM extraction/polish egress (M2 + Phase 7)
fm_premium_identitySensitive — M365 AgentIdentity adapter (real, paid mailboxes) (Phase 8)

The Phase-2 “wedge” default set the tool writes is the first seven (everything except fm_premium_identity); see default_beta_flags() in scripts/fm_beta_enable.py.

Two sensitive flags — treat both as exceptions:

  • fm_llm_extraction is the kill switch on the third-party LLM egress seam itself (every GatewayLLMExtractor model call for capture/email escalation + Ask polish). It is distinct from the per-member third_party_ai consent gate, which always applies regardless of this flag. Enabling it means household data (pseudonymized — member names tokenized, emails/phones/addresses regex-tokenized; see services/mobile/llm_extraction.py) can leave to a model provider. Only enable for a household that has agreed to LLM-assisted extraction.
  • fm_premium_identity provisions real, paid M365 mailboxes (licensing cost). It is pinned OFF in every environment by _fm_default() and is deliberately absent from the tool’s default set. It is refused even when named explicitly unless you pass --force.

How production resolution works (why prod is OFF)

The prod curateme-backend-b2b container runs with ENVIRONMENT=production (docker-compose.production.yml). is_feature_enabled / is_feature_enabled_for_org resolve an FM_* flag in this precedence (highest first):

  1. Per-org overrideorg_feature_flag_overrides doc {org_id, flag_name, enabled} (only the async is_feature_enabled_for_org path reads this; the member routes use it via services/mobile/feature_gate.require_member_feature).
  2. FF_FM_* env vartrue/false env-global kill switch.
  3. _fm_default(flag) — env-aware default. _fm_environment() reads ENVIRONMENTCURATE_ME_ENVAPP_ENV and fails CLOSED: production and any unrecognized label (a typo’d prodution) → every FM_* flag is False. fm_premium_identity is False here regardless of environment.
  4. DEFAULT_FLAGS.

So in production, with no override and no env var, every FM_* flag is False (_fm_default step 3). A per-org override is the surgical way to turn the wedge ON for exactly one household.

Verification: Confirm the box is genuinely production-resolving before relying on overrides:

ssh curateme@178.105.8.25 docker exec curateme-backend-b2b printenv ENVIRONMENT # expect: production

Step 1: Enable the wedge flags for the household

Use the sanctioned tool — it carries the consumer_household guard, the fm_premium_identity refusal, and prints the resolved per-org state. Run it inside the B2B container.

Default wedge set (the first seven flags; fm_premium_identity excluded):

ssh curateme@178.105.8.25 docker exec curateme-backend-b2b \ python scripts/fm_beta_enable.py org_abc123

To enable only a subset:

docker exec curateme-backend-b2b \ python scripts/fm_beta_enable.py org_abc123 --flags fm_documents fm_ask

For each flag the tool upserts one org_feature_flag_overrides row with the exact {org_id, flag_name} filter, setting enabled: true and updated_at (see apply_overrides()). The filter is always the org+flag pair, so the tool can never touch another tenant’s overrides.

If the org is not a consumer_household tenant, the script exits 2 with refused: ... and writes nothing — fix the org id rather than reaching for --force.

Verification: The command prints a per-org flag-state table. Confirm each requested flag shows effective=True, override=True, source=org_override:

flag effective override source fm_assistant_profile True True org_override fm_inbound_email True True org_override ... fm_llm_extraction True True org_override fm_premium_identity False - env_default

effective is computed via the same async is_feature_enabled_for_org path the routes use, so this column is the ground truth for what the household will experience.


Step 2: Verify the override row landed in MongoDB

Cross-check the written rows directly (read-only sanity check), inside the B2B container:

docker exec curateme-backend-b2b python - <<'PY' import asyncio from src.database.mongo_db import get_b2b_db async def main(): coll = get_b2b_db().get_collection("org_feature_flag_overrides") cursor = coll.find({"org_id": "org_abc123"}) async for doc in cursor: print(doc["flag_name"], doc["enabled"], doc.get("updated_at")) asyncio.run(main()) PY

Verification: Each enabled flag appears once with enabled=True. The doc shape is exactly {org_id, flag_name, enabled, updated_at} (lock-step with is_feature_enabled_for_org). fm_premium_identity must not appear unless you deliberately forced it.


Step 3: Confirm the member routes resolve ON for the org

The mobile routers gate on require_member_feature(request, flag), which awaits is_feature_enabled_for_org after auth and raises 404 feature_not_enabled when the surface is not enabled. Re-running the tool’s resolver is the fastest confirmation (no override write):

docker exec curateme-backend-b2b python - <<'PY' import asyncio from src.config.feature_flags import FeatureFlag, is_feature_enabled_for_org async def main(): for flag in (FeatureFlag.FM_INBOUND_EMAIL, FeatureFlag.FM_DOCUMENTS, FeatureFlag.FM_ASK, FeatureFlag.FM_LLM_EXTRACTION): on = await is_feature_enabled_for_org(flag, "org_abc123") print(flag.value, on) asyncio.run(main()) PY

Verification: Each enabled flag prints True. A member-authed request to the corresponding surface (e.g. the household’s app hitting an fm_documents route) now returns its real payload instead of 404 feature_not_enabled. No restart is needed — the override is read live from Mongo on each async resolution.


Rollback / If it goes wrong

Disable the household (per-org kill, surgical)

Write enabled=false overrides for the same org — this beats any env default and immediately re-hides the surfaces:

docker exec curateme-backend-b2b \ python scripts/fm_beta_enable.py org_abc123 --disable

Verification: Re-run Step 1’s table or Step 3’s resolver — effective should now be False with source=org_override. Member routes return 404 feature_not_enabled again.

Accidentally allowlisted a non-household org

The guard normally prevents this. If --force was used in error, run --disable --force for that org, then delete the stray rows:

docker exec curateme-backend-b2b python - <<'PY' import asyncio from src.database.mongo_db import get_b2b_db async def main(): coll = get_b2b_db().get_collection("org_feature_flag_overrides") res = await coll.delete_many({"org_id": "org_WRONG", "flag_name": {"$regex": "^fm_"}}) print("deleted", res.deleted_count) asyncio.run(main()) PY

fm_premium_identity (paid M365) got enabled

If fm_premium_identity shows effective=True for any org, disable it immediately and confirm no mailbox was provisioned. It should never be ON without an explicit, reviewed --force decision (real licensing cost):

docker exec curateme-backend-b2b \ python scripts/fm_beta_enable.py org_abc123 --flags fm_premium_identity --disable --force

Env-global flag set by mistake

If FF_FM_*=true was added to the VPS ~/platform/.env.production (arming all orgs, not just the allowlisted household), remove that line, then redeploy the backend so the container picks up the change:

# On the VPS, edit ~/platform/.env.production — remove the FF_FM_* line. # Then, from your local machine: ./scripts/deploy-to-vps.sh --backend

--backend rebuilds and restarts backend-b2b (plus gateway, runner, celery). After it, re-run Step 3 to confirm only the intended household resolves ON.


  • Agent Identity Provisioning — the M365 provisioning path that fm_premium_identity rides; relevant before enabling premium identity.
  • services/backend/src/config/feature_flags.pyFeatureFlag enum, _fm_default / _fm_environment resolver, is_feature_enabled / is_feature_enabled_for_org.
  • services/backend/scripts/fm_beta_enable.py — the sanctioned per-household allowlist tool (--flags, --disable, --force, --json).
  • services/backend/src/services/mobile/feature_gate.pyrequire_member_feature (the 404 feature_not_enabled gate on member-authed routes).
  • services/backend/src/services/mobile/tenancy.pyCONSUMER_HOUSEHOLD / get_tenant_profile (the tenant guard the tool enforces).