The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
6.7 KiB
Super groups
What they are
Super groups are a feature flag evaluation mechanism that controls enrollment in early access features. Despite the name suggesting "groups of users," they're actually a single person-level property check that determines whether someone has opted into an early access feature.
When a super group condition matches, it takes precedence over all regular feature flag conditions—hence "super." The evaluation returns immediately without checking other conditions.
What they're used for
Super groups power the early access feature (EAF) system. When users opt into an early access feature through the UI, Insights sets a person property like $feature_enrollment/my-feature with the value "true" (stored as a string, not a boolean). The super group condition checks for this property.
Super groups are only added to feature flags for early access features in active stages: Alpha, Beta, and General Availability. The Concept stage allows users to register interest, but does not add super groups – the feature flag remains disabled for opted-in users until the feature is promoted to an active stage.
Example flow:
- Product team creates an early access feature "New Dashboard" in Alpha stage linked to feature flag
new-dashboard - Insights automatically adds a super group to the flag checking
$feature_enrollment/new-dashboard(only because it's in an active stage) - User opts in via the early access features UI
- Insights sets
$feature_enrollment/new-dashboardto"true"on their person - On next flag evaluation, super group matches → user sees the feature
- Regular rollout conditions are never checked for this user
If the feature were in Concept stage instead, steps 3-4 would still occur (user can register interest), but step 2 would not – no super group would be added, so the feature flag would remain disabled.
Note: The condition uses ["true"] (an array) because property filters in Insights always expect array values for the exact operator, even for single values.
This allows opted-in users to see features regardless of percentage rollouts, geography targeting, or other conditions on the flag.
How they are stored
Super groups are stored in the feature flag's filters JSON field alongside regular conditions:
{
"groups": [
{
"properties": [...],
"rollout_percentage": 50
}
],
"super_groups": [
{
"properties": [
{
"key": "$feature_enrollment/new-dashboard",
"type": "person",
"operator": "exact",
"value": ["true"]
}
],
"rollout_percentage": 100
}
]
}
Data type used
Super groups use the same FeatureFlagGroupType as regular conditions:
interface FeatureFlagGroupType {
properties?: AnyPropertyFilter[]
rollout_percentage?: number | null
variant?: string | null
users_affected?: number
sort_key?: string | null
description?: string | null
}
Why this causes confusion
The name doesn't match the purpose
"Super groups" suggests:
- A group-based targeting concept (like "premium users" or "beta testers")
- Something related to Insights's group analytics feature
- A collection of users
What it actually is:
- A single person property check
- Specifically for
$feature_enrollment/{flag_key}properties - An enrollment gate, not a targeting mechanism
A clearer name would be enrollment_condition or early_access_gate.
The type allows more than is supported
Using FeatureFlagGroupType implies you can:
- Add multiple properties (in practice, there's never more than one)
- Use any property key (only
$feature_enrollment/*makes sense) - Use different operators (only
exactwith["true"]is meaningful) - Set different rollout percentages (it's always 100% for enrolled users)
The Django code that creates super groups always produces exactly one property, and only for features in ActiveStage (Alpha, Beta, GA):
# From products/early_access_features/backend/api.py
super_conditions = lambda feature_flag_key: [
{
"properties": [
{
"key": f"$feature_enrollment/{feature_flag_key}",
"type": "person",
"operator": "exact",
"value": ["true"],
},
],
"rollout_percentage": 100,
},
]
While the type system allows an array of properties, this is the only code path that creates super groups, so there's always exactly one property in practice.
It's not group-based at all
This is the most confusing aspect. Insights has a separate "groups" feature for company-level / account-level analytics. Super groups have nothing to do with that:
| Concept | What it means |
|---|---|
| Insights groups | Company/account-level entities for B2B analytics |
| Feature flag groups | Release condition sets (targeting rules) |
| Super groups | Early access enrollment checks |
Three unrelated uses of "group" in feature flags.
Constraints not enforced by the type
The serializer and API enforce constraints that the type doesn't express:
- Early access features can't attach to group-based flags
- Early access features can't have multivariate variants
- Super groups only support person properties
These constraints live in validation code, not the type system, making the valid configurations unclear from the schema alone.
A clearer alternative would be
A dedicated type that reflects what super groups actually do:
interface EarlyAccessEnrollment {
featureFlagKey: string // The key checked in $feature_enrollment/{key}
enabled: boolean // Always true when present
}
Or even simpler, a boolean flag on the feature flag model:
class FeatureFlag(models.Model):
has_early_access_enrollment = models.BooleanField(default=False)
The enrollment property key can be derived from the flag key, eliminating the need for a separate structure.
Key files
| Component | Path |
|---|---|
| Python evaluation | insights/models/feature_flag/flag_matching.py |
| Rust evaluation | rust/feature-flags/src/flags/flag_matching.rs |
| Early access API | products/early_access_features/backend/api.py |
| Frontend types | frontend/src/types.ts |
| Frontend logic | frontend/src/scenes/feature-flags/featureFlagReleaseConditionsLogic.ts |