Skip to content

Rust flags: unsupported cohort groups make evaluation order-dependent #133410

Closed

Description

@marandaneto

Bug Description

The Rust feature-flags service makes dynamic cohort membership depend on the order of children around an unsupported group type. A decisive supported sibling can suppress the error only if it appears before the unsupported group.

This was discussed in SDK-specs #207, which proposes making the current backend behavior a cross-SDK contract. This review suggests fixing the backend instead: treat an unsupported group as unknown, let supported siblings decide when possible, and fail only if the requested cohort remains unresolved.

This issue concerns rust/feature-flags in this repository, not the Rust SDK, and does not request implementing XOR semantics.

How to reproduce

  1. Evaluate a dynamic cohort with these filters using evaluate_dynamic_cohorts:

    {
      "properties": {
        "type": "OR",
        "values": [
          {"type": "XOR", "values": []},
          {"type": "AND", "values": []}
        ]
      }
    }

    An empty supported AND group is true. With the XOR child first, evaluation returns Err(FlagError::CohortFiltersParsingError).

  2. Reverse the two children. Evaluation returns Ok(true).

  3. Repeat with an AND parent and an empty OR child, which is false. Unsupported-first errors; false-first returns Ok(false).

The same issue occurs with populated XOR groups and at both root-supported-group and recursively nested-supported-group evaluation levels.

Expected behavior

Let X be an unsupported XOR group, T a supported matching child, and F a supported nonmatching child:

Children Expected result
OR[X,T] and OR[T,X] true
AND[X,F] and AND[F,X] false
OR[X,F] and OR[F,X] unresolved; retain the existing outward evaluation error
AND[X,T] and AND[T,X] unresolved; retain the existing outward evaluation error

Preserve decisive short-circuiting and supported empty-group behavior. An unsupported group is unknown even when its values are empty. Unknown must not become false merely to let not_in or leaf negation turn it into a match.

Additional context

Relevant backend paths

Source links are pinned to investigated master 9a739f430d1d444b4a39e2908cfaf45cbe3c6b23:

  • Root and nested group evaluation: both evaluators immediately propagate child errors with ?; an unsupported nested group returns CohortFiltersParsingError.
  • Dynamic cohort dependency evaluation: dependencies are evaluated before parents, with boolean-only results and immediate error propagation.
  • Group models: unknown root group types currently fail decoding, whereas nested group types are strings.

Implementation considerations

Changing the group loops alone fixes the inline examples but is not sufficient for referenced cohorts:

  • Preserve an internal unknown state separately from both definitive booleans and unrelated fatal errors. A cohort-local enum or Result<Option<bool>, FlagError> could do this.
  • Carry unknown through referenced-cohort results, not_in, and leaf negation. Otherwise an unresolved dependency still aborts before its parent can use a decisive sibling. The generic dependency graph already supports different result types; a general graph rewrite should not be necessary.
  • Recognize unsupported root types as unknown. During dependency extraction, treat unsupported groups as opaque, rather than letting references inside them introduce unrelated dependency failures before supported siblings can decide the enclosing group.
  • Convert a remaining unknown to the existing error only at the requested-cohort boundary. Do not broadly swallow every CohortFiltersParsingError: it also represents malformed known leaves and exceeded recursion bounds.
  • Keep this scoped to cohort membership. Changing composition across separate ordered release conditions or variant assignments would require separate consideration.

Regression coverage

Extend the existing unknown-group test with both child orders, empty/populated unsupported groups, nested parents, referenced cohorts with unsupported roots, and negation. Keep its lone-XOR error case: a cohort that cannot be decided should still fail. Preserve supported-group, static-membership, malformed-known-leaf, cycle and recursion-limit coverage.

Debug info

  • Source investigation against master 9a739f430d1d444b4a39e2908cfaf45cbe3c6b23.
  • A group-only isolated Rust probe using the exact production bodies of the two group evaluators and item dispatcher reproduced the order dependence. Supporting group types were structural stand-ins; this was not full-service, decoder or dependency-graph execution.
  • Dependency/decoder considerations above were source-traced. No production impact estimate or full-service regression validation is claimed.

Activity

  1. marandaneto commented on Nov 11, 2026

    @marandaneto
    MemberAuthor

    when this is done, the SDKs should:

    • Keeps unsupported-group detection.
    • Allows decisive siblings to resolve the result.
    • Falls back when the result remains unknown.

    lemme know and i can double check the SDKs

  2. posthog commented on Nov 11, 2026

    @posthog
    Contributor

    PostHog self-driving picked up this issue. Status: Research is done.

    Somebody may already be working on it, so ask before you start a fix.

    Report (team access only): https://us.posthog.com/project/2/inbox/reports/01a1ca1c-b77c-721c-9213-d89ae429115e?link_source=github_comment

  3. added a commit that references this issue on Nov 11, 2026
    d7ba35e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions