Good doc, Masha. The direction is right. Here's my take with some additional considerations.
- Registry as source of truth for what flags exist — yes, absolutely
- Validate flag keys on write — reject keys not in registry. This prevents drift
- Merge on read — registry defaults + per-org overrides. This is the core change
- Show ALL registry flags in the admin UI — this is actually the highest-impact change
Your proposal says DynamoDB should only store overrides from default state. There's a subtle but critical distinction here:
Registry: composer.default_enabled = true
DynamoDB: (no record for Org A) → Org A gets true (from default)
If we later change registry to default_enabled = false:
DynamoDB: (still no record for Org A) → Org A suddenly gets false
Risk: Changing a registry default silently flips ALL orgs that haven't been explicitly set. In production, this could disable a feature for hundreds of orgs.
Registry: composer.default_enabled = true
When Org A is created → DynamoDB: Org A, composer = true (explicit copy of default)
If we later change registry to default_enabled = false:
DynamoDB: Org A still has composer = true → Org A keeps the feature
Only NEW orgs get the new default
default_enabled only applies to newly onboarded orgs, not retroactively. This is safer.
Go with Option B (explicit state) for safety. The default_enabled in the registry means: "when a new org is created, pre-populate their flags with these defaults." It does NOT mean "dynamically compute the value at read time."
This matches how LaunchDarkly and most feature flag systems work — defaults are for initialization, not runtime computation.
Agree. The response should include ALL registry flags, not just what's in DynamoDB. For flags without a DynamoDB record, use the registry default_enabled value. This gives the frontend a complete picture.
# Pseudocode for the merge
def get_flags_for_org(org_id):
registry_flags = get_all_registry_flags()
org_overrides = get_org_flags_from_dynamo(org_id) # dict keyed by flag_key
result = []
for flag_def in registry_flags:
if flag_def.key in org_overrides:
# Use the explicit per-org value
result.append({**org_overrides[flag_def.key], "from_registry": True})
else:
# No per-org record — use registry default
result.append({
"key": flag_def.key,
"feature_flag_value": 1 if flag_def.default_enabled else 0,
"user_enabled": flag_def.default_enabled,
"internal_only": flag_def.internal_only, # Always from registry
"from_registry": True,
"is_default": True # Frontend can show this differently
})
return resultAgree. Return 400 if the flag key doesn't exist in the registry:
if flag_key not in FEATURE_FLAG_REGISTRY:
raise HTTPException(400, f"Unknown feature flag: {flag_key}. Must be one of: {list(FEATURE_FLAG_REGISTRY.keys())}")Nice to have. Would need pagination for large numbers of orgs. Not critical for the first iteration — I'd defer this to a separate ticket unless the admin panel specifically needs it.
The current system isn't "wrong" — it works. The issue is that it lacks guardrails (no validation on write, no merge on read). The data model in DynamoDB doesn't need to fundamentally change. The changes are:
- Add validation on write (check key exists in registry)
- Add merge on read (fill in missing flags from registry defaults)
- Move
internal_onlyto registry (already done in your PR) - Frontend: show all registry flags (biggest user-facing change)
- PR #357 (current): Add
composer-dashboard+chatflags, addinternal_onlyto registry, clean up node flags ✅ - Next ticket: Backend — validate flag keys on write, merge on read, move
internal_onlysource of truth to registry - Next ticket: Frontend — show all registry flags, remove "Add flag" freeform input, show
is_defaultindicator - Future: Feature-centric view endpoint, orphaned flag cleanup migration
You'll need a migration plan for flags that exist in DynamoDB but not in the registry. Options:
- Ignore on read: The merge logic already handles this — unknown flags just don't appear in the merged result
- Cleanup migration: One-time script to remove DynamoDB records where the key isn't in the registry
- Soft deprecation: Log a warning when orphaned flags are encountered, clean up later
I'd go with "ignore on read" first, then a cleanup migration once we're confident the registry is complete.