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

No one assigned

    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