Skip to content

Local evaluation: _resolve_flag collapses all identity overrides for a feature into one #239

Description

@ori-perry

Under local evaluation, all but one identity override per feature is silently
dropped — every other identity falls back to the environment default. No exception,
no log, and the dashboard still shows the override as enabled.

Affected: 5.3.0 → 6.2.0. 5.2.0 and earlier are fine.

Where it happens

mappers.py:178-180 gives each identity-override segment a unique dict key but an
empty key field:

segment_key = str(hash(overrides_key))
segment_contexts[segment_key] = SegmentContext(
    key="",  # Identity override segments never use % Split operator

models.py:251-256 then re-keys them by that field:

"segments": {
    segment_context["key"]: segment_context
    for segment_context in overrides_index.get(feature_name, ())
},

All keys are "", so they collapse to one entry — last wins. Project segments are
unaffected; they get a unique str(segment["id"]).

Reproduce

No network. The two identities need different override sets so they land in
separate synthetic segments.

from flagsmith import Flagsmith
from flagsmith.mappers import map_environment_document_to_context

def fs(fid, name, enabled):
    return {"feature": {"id": fid, "name": name}, "enabled": enabled,
            "feature_state_value": None, "featurestate_uuid": f"{name}-{fid}-{enabled}"}

DOC = {
    "api_key": "ser.test_key", "name": "test",
    "feature_states": [fs(1, "flag-a", False), fs(2, "flag-b", False)],
    "project": {"segments": []},
    "identity_overrides": [
        {"identifier": "tenantA", "identity_features": [fs(1, "flag-a", True)]},
        {"identifier": "tenantB", "identity_features": [fs(1, "flag-a", True),
                                                        fs(2, "flag-b", True)]},
    ],
}

client = Flagsmith(environment_key="test_key")
client.enable_local_evaluation = True
client._evaluation_context = map_environment_document_to_context(DOC)

client.get_identity_flags("tenantA").is_feature_enabled("flag-a")  # False — expected True
client.get_identity_flags("tenantB").is_feature_enabled("flag-a")  # True

Dormant until a second identity gets an override on the same feature, then a
working identity silently reverts to the default. all_flags() and remote
evaluation are unaffected. Distinct from #202.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions