Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

01 — Product Overview (Feature Flags Module)

StudyLyon — multi-tenant ERP / School Management API. This package designs the Feature Flags module client (Flutter, forward-looking spec) against the implemented NestJS backend. Every endpoint, DTO field, schema, permission, and wire contract below is derived directly from src/modules/feature-flags/**, src/modules/organizations/**, src/modules/rbac/permissions.constants.ts, src/database/base.repository.ts, src/infrastructure/bullmq/queue.constants.ts, and the studylyon-blueprint handbook. No feature is invented; gaps are flagged in Assumptions & Open Questions and marked (planned) / (forward-looking) / (proposed) per global rules.


1. Purpose

Feature Flags let a tenant (institution) turn optional platform capabilities on and off — biometric attendance, SMS, WhatsApp channels, AI reports, payroll, inventory, and similar channel/integration toggles (blueprint 03-Database/COLLECTIONS.md:785-801). It is the configuration surface for "gradual rollout" and capability gating: downstream services are supposed to read a flag before offering a feature (PLAN.md:29 — scenario 2.6 "Configure feature flags (biometric, SMS, etc.) … Feature-toggle read in downstream services").

ResponsibilitySource
CRUD of tenant feature toggles (key, enabled, label, description, module)feature-flag.schema.ts:9-22
List all / by module / enabled-only flag setsfeature-flags.controller.ts:23-34
Keyed upsert (create-or-update, merge on enabled+label)feature-flag.repository.ts:32-43
Bulk upsert (sequential, no transaction)feature-flags.service.ts:37-45
Soft-delete of a flagfeature-flags.service.ts:47-52
Evaluation primitive for downstream servicesfeature-flags.service.ts:22-25 isEnabled()
Org-level flag map (lifecycle surface)organizations.controller.ts:74-89, organizations.service.ts:139-154
RBAC permission names (declared)permissions.constants.ts:78-80

There are two distinct flag surfaces in the backend, and they are not connected in code:

  1. Flag store — the feature_flags collection (feature-flag.schema.ts:7): a per-tenant catalog of toggles with metadata. Served by /api/v1/feature-flags*.
  2. Org lifecycle overlayorganizations.metadata.featureFlags: Record<string, boolean> (organization.schema.ts:129-130), served by GET/PATCH /api/v1/organizations/:id/feature-flags. PATCH full-replaces the map (organizations.service.ts:149-151).

FeatureFlagsService.isEnabled() reads only the flag store (feature-flags.service.ts:22-25). The blueprint assigns "Feature flag evaluation" to the Organizations module (blueprint 04-Modules/Organizations.md:17) — this integration does not exist yet (OQ-2).

2. Business goals

GoalMeasure
Tenant can enable/disable channel features without a deployflag toggle round-trip < 1 s p95 (single Mongo upsert, no cache layer today)
Flags are per-tenant, never cross-tenantevery read scoped by tenantId from JWT (base.repository.ts:20-30)
Flags survive tenant reuse of keysunique {tenantId, key} index (feature-flag.schema.ts:26)
Feature rollout can be staged per modulemodule field + {tenantId, module} index (feature-flag.schema.ts:22,27)
Downstream services gate on flagsisEnabled() primitive exists (feature-flags.service.ts:22-25); no consumer yet (OQ-1)
Config changes are auditableflag toggles listed under Config audit category — (planned) (AUDITING.md:52)
Cache keeps flag evaluation cheapsl:global:featureflag:{name}, poll + cache, 30 s TTL — (planned) (CACHE_ARCHITECTURE.md:15,33,48)

3. User goals

  • Org admin: browse all toggles for my school, see what's on/off per module, flip a channel (biometric, SMS, WhatsApp) and trust it applies.
  • Feature configurator (admin delegate): edit labels/descriptions, batch-enable a rollout (bulk update), fix a mistake (rollback a flag).
  • Platform admin: read flags across tenants during support; platform-level toggles.
  • End users (teacher/staff/parent/student): never see a disabled feature — UI hides or explains it; no crashes when a flag flips mid-session.

4. Stakeholders

Platform operator (SaaS vendor — tenant tiers/plan gating (proposed)), institution admins, downstream module owners (biometric, notifications, payments — all forward-looking), support staff (misconfigured channels), QA + design + engineering consuming these docs.

5. Why this exists

One codebase serves thousands of institutions with different capabilities (school vs coaching (planned) matrix, IMPLEMENTATION_PLAN.md:310-336) and different channel entitlements (WhatsApp BSP approval, biometric hardware availability — FEATURE_ROADMAP.md:54-57). Flags decouple code presence from feature availability without per-tenant branches. The blueprint lists "Feature toggles / Gradual rollout / A/B testing" as the module's responsibilities (MODULE_ARCHITECTURE.md:304-312); only the toggle CRUD exists in code — gradual rollout and A/B testing are (planned) (OQ-6). The roadmap slots the framework in Phase 3 (FEATURE_ROADMAP.md:42).

6. Dependencies

DependencyRoleSource
Auth (JwtAuthGuard, global)every flag endpoint requires bearer JWTfeature-flags.module.ts:12, app.module.ts:130
RBAC (global RbacGuard)permission check — no flag endpoint declares @Permissions → effectively JWT-only (OQ-5)app.module.ts:131, rbac.guard.ts:29
Rate limit (global)api tier default 100/minrate-limit.constants.ts:6, rate-limit.guard.ts:36
TenantContextServicetenant isolation + platform-admin bypassbase.repository.ts:20-30
Mongoose feature_flagssystem of recordfeature-flags.module.ts:13-15
Redis + BullMQ cache-rebuilddeclared, not wired to flags — flag cache invalidation (planned)queue.constants.ts:13, scheduler.service.ts:27,67
Organizations moduleorg-level flag overlay surfaceorganizations.controller.ts:74-89

7. Success metrics

  • Flag list/filter/read latency < 200 ms p95 (direct Mongo, no cache today — CACHE_ARCHITECTURE.md:48 proposes 30 s cache).
  • Toggle write p95 < 1 s; bulk of ≤ 20 flags applies in < 3 s (sequential loop, OQ-3).
  • Zero cross-tenant flag leakage (scopedFilter enforced).
  • isEnabled() is consumed by ≥ 1 downstream module (biometric / notifications) — target; no consumer today (OQ-1).
  • No 5xx storm from the delete→re-create E11000 edge (OQ-4).
  • Flag-change propagation to client ≤ 30 s (poll TTL) once cache lands.

8. Edge cases (client-relevant)

  • Upsert drops description and module: DTO accepts them (update-feature-flag.dto.ts:13-26) but FeatureFlagRepository.upsert only $sets enabled and label (feature-flag.repository.ts:40). Editing a flag's description/module silently persists nothing.
  • PUT /feature-flags with a new key = create (upsert); enabled is required → you cannot create a "disabled-by-default" flag without sending enabled:false.
  • Bulk update is sequential, no transaction (feature-flags.service.ts:40-44): a failing item mid-list leaves earlier writes applied.
  • Delete is soft: flag vanishes from every list (base.repository.ts:20-30), but the unique {tenantId,key} index slot persists — re-creating the same key after delete can hit E11000 → 500 (OQ-4).
  • Org-level PATCH full-replaces: flags omitted from the body are lost (organizations.service.ts:149-151).
  • Missing key → 404 RESOURCE_NOT_FOUND ("Feature flag "" not found." — feature-flags.service.ts:29).
  • Platform admin: reads bypass tenant scope (base.repository.ts:21-23), but upsert calls requireTenantId() directly (feature-flag.repository.ts:37) — a platform-admin write without a tenant context fails (OQ-5).

9. Assumptions (module)

  • The client (Flutter) is forward-looking; the backend surface is complete and is the contract source.
  • feature-flags.read/update/delete are the canonical permission names (permissions.constants.ts:78-80); the UI is designed so the server can start enforcing them without UI redesign (OQ-5).
  • The feature_flags collection has no seed data in code — tenants start with an empty catalog; the UI must render a useful empty state and the client ships no hardcoded keys. Default per-institution matrices are (planned) (IMPLEMENTATION_PLAN.md:310-336).
  • Flag evaluation cache (30 s poll, sl:global:featureflag:{name}) is documented in the blueprint, not implemented (CACHE_ARCHITECTURE.md:33,48); the client must therefore treat server responses as immediately authoritative and re-fetch per its own TTL.
  • "Gradual rollout" and "A/B testing" are blueprint responsibilities (MODULE_ARCHITECTURE.md:310-312) with no API support — no percentage/audience fields exist on the schema (feature-flag.schema.ts:9-22).

10. Open questions (module-level; global ledger in 00-shared/12)

#ItemImpact
OQ-1isEnabled() has no consumers in src/ today (grep: only feature-flags.service.ts:22). Which downstream module (biometric, notifications, payments) evaluates flags first, and when?All "disabled-feature" UX in this package
OQ-2Org lifecycle overlay (organizations.metadata.featureFlags) is disconnected from the flag store. Which surface wins? Should PATCH /organizations/:id/feature-flags merge instead of full-replace?Affected-features map, admin confusion
OQ-3Bulk update: sequential awaits, no transaction/rollback (feature-flags.service.ts:40-44). Accept partial apply, or wrap in a transaction?Bulk UX (which rows succeeded)
OQ-4Re-creating a soft-deleted key hits the unique {tenantId,key} index → E11000 → 500 (feature-flag.repository.ts:38-42). Permanent delete endpoint, or hard-delete in upsert, or accept 500?Rollback journey
OQ-5feature-flags.* permissions exist (permissions.constants.ts:78-80) but no endpoint declares @Permissions(...) (rbac.guard.ts:29 passes when metadata is absent). When is enforcement added? Also: platform-admin upsert (tenantId requirement).Route gating, admin UX
OQ-6Gradual rollout / A/B testing (MODULE_ARCHITECTURE.md:310-312): no fields, no endpoints. Planned phase?Flag editor fields (percentage/audience)
OQ-7No WS topic for flag changes (00-shared/07 §8 topic list); no domain event (FeatureFlagChanged) in code. Realtime propagation (planned)?Client TTL vs push

11. Glossary (this module)

TermMeaning
FlagOne feature_flags doc: key, enabled, label?, description?, module? + BaseSchema fields
KeyUnique per tenant identifier (biometric.enabled, channels.whatsapp — examples only; keys are free-form strings, update-feature-flag.dto.ts:5-7)
UpsertfindOneAndUpdate(..., {upsert:true}) — insert or $set merge on enabled+label (feature-flag.repository.ts:38-42)
Enabled setThe list returned by GET /feature-flags/enabled — the client's gate data
Flag storeThe feature_flags collection (module surface)
Org overlayorganizations.metadata.featureFlags map (lifecycle surface, full-replace semantics)
Soft deleteisDeleted:true; excluded from all scoped queries (base.repository.ts:20-30)
Envelope{success,message,data,meta?,timestamp,requestId} (response-envelope.interceptor.ts:50-59)