Dependent flags let a segment condition read another flag's result via a $.flags.<feature name>.<enabled|value|variant> property, so that flag has to be resolved before the condition is evaluated. Behaviour is defined by the shared cases in Flagsmith/engine-test-data#59; schema in Flagsmith/flagsmith#8396; reference implementation in Flagsmith/flagsmith-engine#343.
The reference implementation resolves a flag lazily, on first read, rather than scanning every condition up front to discover dependencies. ojg supports this directly — jp/get.go dispatches on jp.Keyed, so a lazy Flags type resolves on read with no property parsing. Two things in this engine currently get in the way:
getContextValue builds its getter per call, so jp.ParseString runs on every lookup — about 20% of a lookup and 10 of its 16 allocations.
client.go keeps a single *EngineEvaluationContext in an atomic.Value, shared across goroutines. Resolved flags written onto that struct would be written concurrently by every in-flight evaluation.
For reference, pre-resolving all flags instead costs a flat ~12 µs and ~49 KB per evaluation, against ~400 ns per flag actually read; lazy resolution via jp.Keyed and via parsed-Expr inspection measure the same, so either is fine.
Acceptance criteria
Engine:
engine-test-data is bumped to the tag containing the flag dependency cases, and they pass.
- An environment with no
$.flags conditions gains no per-evaluation cost.
- Resolved flags are held per evaluation, never written to the context shared via
atomic.Value.
- Parsed JSONPath expressions are cached per property string.
SDK:
GetFlags does not skip segments anymore.
Dependent flags let a segment condition read another flag's result via a
$.flags.<feature name>.<enabled|value|variant>property, so that flag has to be resolved before the condition is evaluated. Behaviour is defined by the shared cases in Flagsmith/engine-test-data#59; schema in Flagsmith/flagsmith#8396; reference implementation in Flagsmith/flagsmith-engine#343.The reference implementation resolves a flag lazily, on first read, rather than scanning every condition up front to discover dependencies.
ojgsupports this directly —jp/get.godispatches onjp.Keyed, so a lazyFlagstype resolves on read with no property parsing. Two things in this engine currently get in the way:getContextValuebuilds its getter per call, sojp.ParseStringruns on every lookup — about 20% of a lookup and 10 of its 16 allocations.client.gokeeps a single*EngineEvaluationContextin anatomic.Value, shared across goroutines. Resolved flags written onto that struct would be written concurrently by every in-flight evaluation.For reference, pre-resolving all flags instead costs a flat ~12 µs and ~49 KB per evaluation, against ~400 ns per flag actually read; lazy resolution via
jp.Keyedand via parsed-Exprinspection measure the same, so either is fine.Acceptance criteria
Engine:
engine-test-datais bumped to the tag containing the flag dependency cases, and they pass.$.flagsconditions gains no per-evaluation cost.atomic.Value.SDK:
GetFlagsdoes not skip segments anymore.