Skip to main content

Resolution

For a given (flag, environment, evaluation context) triple, exactly one variant is returned. This page is the reference for how that variant is chosen: the four-step algorithm, the catch-all behavior, and the testing rollout gate.

The per-block field reference lives in flag § [flag.environments.<env>] blocks. This page is the algorithm.


The four-step algorithm

For an evaluation against environment <env>, with the eval-time option include_testing (default false — see § The rollout workflow):

  1. Env rules. If [flag.environments.<env>] exists AND declares a rules array AND either testing = false (the default) OR the caller passed include_testing = true, walk the array in declared order. For each rule:
    • Evaluate the rule's audience (segment lookup OR inline predicate) against the evaluation context.
    • If the audience matches, return the rule's variant. rule_matched = "rule:<index>" (zero-based).
  2. Env default. If [flag.environments.<env>] declares variant, return it. rule_matched = "default".
  3. _ rules. Reached only when step 1 was skipped because <env> did not declare its own rules. Walk [flag.environments._.rules] in declared order. First match → return its variant with rule_matched = "rule:<index>" (indexed against _'s array).
  4. _ default. Return [flag.environments._].variant. rule_matched = "default".

Short-circuits

Three short-circuits in the walk:

  • Env declared rules → step 3 is skipped. The env's rules completely replace _'s rules; there is no layering. The skip is based on the declared manifest shape, not on whether the rules array actually fired for this caller — so testing = true + no opt-in still skips step 3.
  • Env declared variant → step 4 is skipped. The env's default completely replaces _'s default.
  • testing = true + no include_testing → step 1 is skipped (the env's rules are invisible to this caller), but steps 2–4 proceed with their normal skip rules. Non-opted-in callers see the env's declared variant if any, else _'s variant. The env's testing rules never leak to non-opted-in callers, and _'s rules never apply to non-opted-in callers in a testing = true env.

Combinatorial summary

<env> block declares…Resolution path
Nothing (or no block)step 3 → step 4
variant onlystep 2 (_ never consulted)
rules onlystep 1 → step 4
rules + variantstep 1 → step 2 (_ never consulted)
testing = true + rules + caller passes include_testingstep 1 → step 2 / step 4
testing = true + rules + caller omits include_testingstep 2 if variant, else step 4 (rules invisible; step 3 stays skipped)

The catch-all _

_ is the env block that applies when the requested environment did not supply a piece of the answer. It MUST exist on every flag, and it MUST declare a variant.

_ is reserved as an env name regardless of mode (typed or untyped). The linter does NOT require _ to appear in [namespace.environments] — the reserved name is structural.

The simplest possible flag declares only [flag.environments._] with a variant and no rules. Resolution then returns the same variant for every env.


Worked traces

Catch-all only

[flag.environments._]
variant = "off"

[[flag.environments._.rules]]
segment = "internal-employees"
variant = "on"
EvaluationTraceResult
staging env (no staging block), internal employeestep 1 skipped → step 2 skipped → step 3 _ rule matches → "on""on"
staging env, external userstep 1 skipped → step 2 skipped → step 3 no match → step 4 _ default"off"

Production with rules only

Adding a production block with rules but no variant:

[flag.environments.production]
[[flag.environments.production.rules]]
segment = "checkout-redesign-rollout-10"
variant = "on"
EvaluationTraceResult
production, in rollout-10step 1 match → "on""on"
production, internal employee NOT in rollout-10step 1 no match → step 2 skipped → step 3 skipped (env declared rules) → step 4 _ default"off"

The _ rule for internal-employees does NOT fire in production because production declared its own rules array. Internal employees who need "on" in production must have a matching rule duplicated into production's block. This is intentional: env rules are a complete override, not an additive layer.

Self-contained production

Adding a variant to production:

[flag.environments.production]
variant = "off"

[[flag.environments.production.rules]]
segment = "checkout-redesign-rollout-10"
variant = "on"
EvaluationTraceResult
production, in rollout-10step 1 match → "on""on"
production, anyone elsestep 1 no match → step 2 production default"off"

production is now fully self-contained; _ is never consulted.

Production kill-switch

Pin an env to a single variant — the kill-switch shape:

[flag.environments.production]
variant = "off"

production has no rules; step 1 is skipped, step 2 returns "off" immediately. _'s rules and default are never consulted in production.


The rollout workflow

A flag in a given env progresses through three observable states. Each state corresponds to a specific env-block shape; there is no state field — the shape is the state.

StateEnv-block shapeBehavior
Disabledvariant = "<off>", no rules (the kill-switch shape)Step 1 empty (no rules), step 2 returns the off-variant. _ never consulted.
Testingtesting = true, rules declared, variant strongly recommendedStep 1 evaluates only for callers that pass include_testing = true; everyone else sees step 2 or step 4.
Enabledrules declared (and optionally variant); testing absent or falseStep 1 evaluates for every caller.

A typical rollout flips through the three states by editing only the env block, with no churn on rules or variants:

# Day 0 — disabled in production.
[flag.environments.production]
variant = "off"
# Day 1 — testing in production. Internal admins opt in via the SDK call.
# Everyone else still sees `off`. The `_` catch-all is never consulted for
# this env because the production block declares its own `rules` (step 3
# stays skipped).
[flag.environments.production]
testing = true
variant = "off"

[[flag.environments.production.rules]]
description = "Admin preview"
segment = "internal-admins"
variant = "on"
# Day 7 — testing pronounced safe; flip the gate off. Same rules, same
# variant. Now every prod request walks the rules.
[flag.environments.production]
variant = "off"

[[flag.environments.production.rules]]
description = "Admin preview"
segment = "internal-admins"
variant = "on"

The opt-in is per-eval-call, not a token scope or namespace setting: the same admin user evaluating the same flag with include_testing = false still sees off. This keeps the opt-in scoped to the explicit UI surface (typically an internal admin tool) and prevents accidental leakage through unrelated code paths.

Caller surfaces

Every SDK and the server's POST /evaluate{,/all} body accept the opt-in. Calls without it default to include_testing = false.

SurfaceOpt-in syntax
Rust SDKNamespace::eval_with_options(flag, env, ctx, EvalOptions::new().with_include_testing(true)). Typed variants: eval_bool_with_options, eval_string_with_options, etc.
TypeScript SDKclient.eval(flag, env, ctx, { includeTesting: true }). Same includeTesting field on evalBool, evalString, evalI64, evalF64, evalJson, evalAll.
Server POST /evaluate{,/all}Top-level body field "include_testing": true. No auth-scope change.
ExdRemote (no-WASM)Same includeTesting field passes through to the server's body.

What linters and telemetry see

  • Lint emits E039 when testing = true is paired with no rules — the gate has nothing to gate, almost always an editing mistake.
  • [flag.environments._] cannot declare testing = true. _ is the catch-all for non-opted-in traffic in every other env; turning it into a testing env would have no audience. Lint emits E039 for testing = true on _.
  • Telemetry records carry the resolved rule_matched exactly as resolution computes it; non-opted-in callers in a testing env land in step 2 / step 4 just as they would in a non-testing env. There is no special "testing-gated" reason variant.

Where testing does NOT change behavior

bucketing_attribute resolution (the identifier the SDK uses for bucket segments) walks every rule's segment graph regardless of testing. The bucket the SDK picks for a given entity is the same whether or not the caller passes include_testing. Telemetry consistency for opted-in vs. non-opted-in callers depends on this; do not "fix" it.


Missing environment blocks

A missing [flag.environments.<env>] for a named env is not an error condition. Resolution skips steps 1–2 (no env block to consult) and falls through to steps 3–4 (_'s rules and default). Authors add an env block only where behavior actually differs from _.

In typed-env mode, the linter does NOT warn on missing per-flag env blocks; _ provides the answer for every undeclared env.

A flag with no env blocks at all is E037[flag.environments._] is missing.


A multi-environment worked example

schema_version = "0.1"

[flag]
type = "boolean"
description = "Routes /checkout to the redesigned flow"
owner = "payments"
lifecycle = "active"

[flag.variants]
on = true
off = false

# Catch-all: off everywhere, with internal employees opted in.
[flag.environments._]
variant = "off"

[[flag.environments._.rules]]
description = "Internal employees"
segment = "internal-employees"
variant = "on"

# Pre-prod: always on.
[flag.environments.development]
variant = "on"

[flag.environments.staging]
variant = "on"

# Production: gradual rollout, fully self-contained.
[flag.environments.production]
variant = "off"

[[flag.environments.production.rules]]
description = "10% rollout to general population"
segment = "checkout-redesign-rollout-10"
variant = "on"

Traces:

  • development, any user → step 2 returns "on". production not consulted. _ not consulted (development declared its own variant).
  • staging, any user → same as development.
  • production, in rollout-10 → step 1 returns "on".
  • production, internal employee not in rollout-10 → step 1 no match → step 2 returns "off". The internal-employees rule on _ is not consulted.
  • qa (no env block at all) → step 1 and step 2 skipped → step 3 walks _'s rules → internal employees get "on", everyone else gets "off" via step 4.

To make internal employees "on" in production too, duplicate the rule into production's rules array — there is no inheritance from _.


Reading rule_matched in telemetry

Evaluation records carry rule_matched. Possible values:

ValueMeaning
"rule:<i>" (env's rules)Step 1 matched at the <i>-th rule of [flag.environments.<env>].rules.
"default" (env's default)Step 2 returned [flag.environments.<env>].variant.
"rule:<i>" (catch-all rules)Step 3 matched at the <i>-th rule of [flag.environments._.rules].
"default" (catch-all default)Step 4 returned [flag.environments._].variant.
"sdk_default"The SDK could not load the manifest and fell back to the caller's compile-time default.
"attr_type_mismatch"A runtime attribute type did not match the manifest's inferred type. See evaluation-context § Inferred attribute types.

The reason field plus the matched rule's description is usually enough to understand any evaluation. Combine with manifest_version in the record to know which manifest snapshot was in effect.


See also