diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index d5bead3..aaf7e6c 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -44,6 +44,12 @@ jobs:
- name: Verify published artifact boundaries
run: bash scripts/verify-artifacts.sh
+ - name: Execute packaged SDK consumer
+ run: bash scripts/test-runtime-minimum.sh ${{ matrix.java }}
+
+ - name: Check README initialization
+ run: bash scripts/test-readme-initialization.sh
+
example:
name: Featurevisor example-1
runs-on: ubuntu-latest
@@ -72,7 +78,7 @@ jobs:
run: |
mkdir example-1
cd example-1
- npx --yes @featurevisor/cli@3 init --example=1
+ npx --yes @featurevisor/cli@latest init --example=1
npm install
npx featurevisor build
npx featurevisor test --onlyFailures
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index ecc9113..f3b5ff3 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -36,23 +36,8 @@ jobs:
id: version
shell: bash
run: |
- if [[ ! "$GITHUB_REF_NAME" =~ ^v4\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
- echo "Expected a Featurevisor Java v4 semantic version tag" >&2
- exit 1
- fi
+ bash scripts/verify-release.sh "$GITHUB_REF_NAME"
version="${GITHUB_REF_NAME#v}"
- if ! grep -Fq "$version" pom.xml; then
- echo "Tag $GITHUB_REF_NAME does not match the Maven revision" >&2
- exit 1
- fi
- if ! grep -Fq "$version" README.md; then
- echo "README installation version does not match $GITHUB_REF_NAME" >&2
- exit 1
- fi
- if ! grep -Fq "version = \"$version\"" featurevisor-sdk/src/main/java/com/featurevisor/cli/CLI.java; then
- echo "CLI version does not match $GITHUB_REF_NAME" >&2
- exit 1
- fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Publishing Featurevisor Java $version"
diff --git a/.nvmrc b/.nvmrc
index 209e3ef..a45fd52 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20
+24
diff --git a/README.md b/README.md
index a12058b..a8589d0 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,16 @@
# Featurevisor Java SDK
-This is a port of Featurevisor [Javascript SDK](https://featurevisor.com/docs/sdks/javascript/) v3.x to Java, providing a way to evaluate feature flags, variations, and variables in your Java applications.
-
-This SDK supports Featurevisor v3 behaviour and v2 datafiles. Generated datafiles continue to carry `schemaVersion: "2"`.
+Use the Featurevisor Java SDK to evaluate flags, variations and variables in Java applications.
## Table of contents
- [Installation](#installation)
-- [Public API](#public-api)
- [Repository](#repository)
- [Dependency](#dependency)
- [Authentication](#authentication)
- [Initialization](#initialization)
-- [Evaluation types](#evaluation-types)
+ - [Public API](#public-api)
+ - [Evaluation types](#evaluation-types)
- [Context](#context)
- [Setting initial context](#setting-initial-context)
- [Setting after initialization](#setting-after-initialization)
@@ -21,10 +19,11 @@ This SDK supports Featurevisor v3 behaviour and v2 datafiles. Generated datafile
- [Check if enabled](#check-if-enabled)
- [Getting variation](#getting-variation)
- [Getting variables](#getting-variables)
+ - [Getting feature variables](#getting-feature-variables)
- [Type specific methods](#type-specific-methods)
-- [Getting global variables](#getting-global-variables)
-- [Getting aggregate evaluations](#getting-aggregate-evaluations)
-- [Sticky features and variables](#sticky-features-and-variables)
+ - [Getting global variables](#getting-global-variables)
+- [Getting evaluations](#getting-evaluations)
+- [Sticky](#sticky)
- [Setting datafile](#setting-datafile)
- [Merging by default](#merging-by-default)
- [Replacing](#replacing)
@@ -51,6 +50,7 @@ This SDK supports Featurevisor v3 behaviour and v2 datafiles. Generated datafile
- [Test](#test)
- [Benchmark](#benchmark)
- [Assess distribution](#assess-distribution)
+- [GitHub repositories](#github-repositories)
- [Development of this package](#development-of-this-package)
- [Setting up](#setting-up)
- [Running tests](#running-tests)
@@ -61,6 +61,8 @@ This SDK supports Featurevisor v3 behaviour and v2 datafiles. Generated datafile
## Installation
+Upgrading an existing application? Follow the [Java migration guide](https://featurevisor.com/docs/migrations/v4/java/).
+
In your Java application, update `pom.xml` to add the following:
### Repository
@@ -86,12 +88,12 @@ Add the Featurevisor Java SDK as a dependency with your desired version:
com.featurevisor
featurevisor-java
- 4.1.0
+ 5.0.0
```
-Find latest version here: [https://github.com/featurevisor/featurevisor-java/packages](https://github.com/featurevisor/featurevisor-java/packages)
+Browse [Java SDK packages](https://github.com/featurevisor/featurevisor-java/packages)
### Authentication
@@ -115,72 +117,76 @@ To authenticate with GitHub Packages, in your `~/.m2/settings.xml` file, add the
```
-You can generate a new GitHub token with `read:packages` scope here: [https://github.com/settings/tokens](https://github.com/settings/tokens)
+Generate a token with `read:packages` scope in [GitHub token settings](https://github.com/settings/tokens)
Example applications:
- [Java application](https://github.com/featurevisor/featurevisor-example-java)
- [Kotlin Android application](https://github.com/featurevisor/featurevisor-example-android)
-## Public API
+## Initialization
-The main runtime API is `Featurevisor.createFeaturevisor()`:
+Read a built [datafile](https://featurevisor.com/docs/building-datafiles/) from a local file and pass its content to the SDK. Save this Java 11 example as `Main.java` and place `featurevisor-all.json` in your working directory:
```java
-import com.featurevisor.sdk.FeaturevisorLogLevel;
-
-Featurevisor f = Featurevisor.createFeaturevisor(
- new Featurevisor.FeaturevisorOptions().datafile(datafileContent)
-);
+import com.featurevisor.sdk.Featurevisor;
+import com.featurevisor.sdk.DatafileContent;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ String json = Files.readString(Path.of("featurevisor-all.json"));
+ DatafileContent datafileContent = DatafileContent.fromJson(json);
+ Featurevisor f = Featurevisor.createFeaturevisor(
+ new Featurevisor.FeaturevisorOptions().datafile(datafileContent)
+ );
+ try {
+ System.out.println(f.isEnabled("checkout", Map.of("userId", "123")));
+ } finally {
+ f.close();
+ }
+ }
+}
```
-Most applications only need `Featurevisor.createFeaturevisor`, the `Featurevisor` instance type, and `Featurevisor.FeaturevisorOptions`. Public extension and observability types include `FeaturevisorModule`, `FeaturevisorDiagnostic`, and the datafile model types.
+File read and JSON parsing failures propagate to the caller. You can also load the content with Java's HTTP client. Check for a successful HTTP status before passing the response body to the SDK. The following sections cover the available options.
-Concurrent evaluations are safe after an instance is configured. Do not call state changing methods such as `setDatafile`, `setContext`, `setStickyFeatures`, `setStickyVariables`, `addModule`, `removeModule`, or `close` concurrently with evaluations or with each other. Apply those changes from a serialized update path. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
-
-## Initialization
+### Public API
-The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly:
+The main runtime API is `Featurevisor.createFeaturevisor()`:
```java
-import com.featurevisor.sdk.Featurevisor;
-
-// Load datafile content
-String datafileUrl = "https://cdn.yoursite.com/datafile.json";
-String datafileContent = "..." // load your datafile content
+import com.featurevisor.sdk.FeaturevisorLogLevel;
-// Create SDK instance
Featurevisor f = Featurevisor.createFeaturevisor(
new Featurevisor.FeaturevisorOptions().datafile(datafileContent)
);
```
-or by constructing a `Featurevisor.FeaturevisorOptions` object:
-
-```java
-Featurevisor f = Featurevisor.createFeaturevisor(new Featurevisor.FeaturevisorOptions()
- .datafile(datafileContent)
-);
-```
+Most applications only need `Featurevisor.createFeaturevisor`, the `Featurevisor` instance type, and `Featurevisor.FeaturevisorOptions`. Public extension and observability types include `FeaturevisorModule`, `FeaturevisorDiagnostic`, and the datafile model types.
-We will learn about several different options in the next sections.
+Concurrent evaluations are safe after an instance is configured. Do not call state changing methods such as `setDatafile`, `setContext`, `setStickyFeatures`, `setStickyVariables`, `addModule`, `removeModule`, or `close` concurrently with evaluations or with each other. Apply those changes from a serialized update path. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.
-## Evaluation types
+### Evaluation types
-We can evaluate flags, variations, variables inside features, and [global variables](https://featurevisor.com/docs/global-variables/):
+Featurevisor evaluates four kinds of values:
- [**Flag**](#check-if-enabled) (`boolean`): whether the feature is enabled or not
-- [**Variation**](#getting-variation) (`Object`): the variation of the feature (if any)
-- [**Variables**](#getting-variables): variable values of the feature (if any)
-- [**Global variables**](#getting-global-variables): reusable values that are not owned by a feature
+- [**Variation**](#getting-variation) (`String`): the variation of the feature (if any)
+- [**Feature variable**](#getting-feature-variables): a configuration value owned by a feature
+- [**Global variable**](#getting-global-variables): a configuration value evaluated independently of a feature
These evaluations are run against the provided context.
## Context
-Contexts are [attribute](https://featurevisor.com/docs/attributes) values that we pass to SDK for evaluating [features](https://featurevisor.com/docs/features) against.
+Contexts are [attribute](https://featurevisor.com/docs/attributes) values you pass to the SDK for evaluating [features](https://featurevisor.com/docs/features) against.
+
+Conditions compare these attributes using the [operators](https://featurevisor.com/docs/operators/), including `empty` and `notEmpty`.
-Think of the conditions that you define in your [segments](https://featurevisor.com/docs/segments/), which are used in your feature's [rules](https://featurevisor.com/docs/features/#rules).
+Date comparisons with `before` and `after` accept portable timestamp strings, `Instant`, `java.util.Date` (including `java.sql.Timestamp`), `OffsetDateTime` and `ZonedDateTime`. Native values are compared by their instant and truncated to milliseconds without rounding. Both the native written year, where present, and the UTC year must be within 1 to 9999. Explicit native offsets must have a magnitude of at most 14 hours, including any seconds. Offsets within that limit retain their full precision. `Instant` and `java.util.Date` have only an instant, so no offset check applies. `LocalDate` and `LocalDateTime` are not accepted because they do not identify an instant. Invalid values make both operators return `false`.
They are plain maps:
@@ -191,11 +197,9 @@ context.put("country", "nl");
// ...other attributes
```
-Context can be passed to SDK instance in various different ways, depending on your needs:
-
### Setting initial context
-You can set context at the time of initialization:
+Set context when you create the instance:
```java
Map initialContext = new HashMap<>();
@@ -211,7 +215,7 @@ This is useful for values that don't change too frequently and available at the
### Setting after initialization
-You can also set more context after the SDK has been initialized:
+Add context later:
```java
Map additionalContext = new HashMap<>();
@@ -225,7 +229,7 @@ This will merge the new context with the existing one (if already set).
### Replacing existing context
-If you wish to fully replace the existing context, you can pass `true` in second argument:
+Replace the context by passing `true` as the second argument:
```java
Map newContext = new HashMap<>();
@@ -239,28 +243,26 @@ f.setContext(newContext, true); // replace existing context
### Manually passing context
-You can optionally pass additional context manually for each and every evaluation separately, without needing to set it to the SDK instance affecting all evaluations:
+Pass context to one evaluation without changing the instance context:
```java
Map context = new HashMap<>();
context.put("userId", "123");
context.put("country", "nl");
-boolean isEnabled = f.isEnabled("my_feature", context);
-String variation = f.getVariation("my_feature", context);
-String variableValue = f.getVariableString("my_feature", "my_variable", context);
+boolean isEnabled = f.isEnabled("checkout", context);
+String variation = f.getVariation("checkout", context);
+String variableValue = f.getVariableString("checkout", "bgColor", context);
```
When manually passing context, it will merge with existing context set to the SDK instance before evaluating the specific value.
-Further details for each evaluation types are described below.
-
## Check if enabled
-Once the SDK is initialized, you can check if a feature is enabled or not:
+Check whether a feature is enabled:
```java
-String featureKey = "my_feature";
+String featureKey = "checkout";
boolean isEnabled = f.isEnabled(featureKey);
@@ -269,7 +271,7 @@ if (isEnabled) {
}
```
-You can also pass additional context per evaluation:
+Pass additional context to one evaluation:
```java
Map additionalContext = new HashMap<>();
@@ -280,10 +282,10 @@ boolean isEnabled = f.isEnabled(featureKey, additionalContext);
## Getting variation
-If your feature has any [variations](https://featurevisor.com/docs/features/#variations) defined, you can evaluate them as follows:
+Evaluate a feature's [variations](https://featurevisor.com/docs/features/#variations):
```java
-String featureKey = "my_feature";
+String featureKey = "checkout";
String variation = f.getVariation(featureKey);
@@ -294,7 +296,7 @@ if ("treatment".equals(variation)) {
}
```
-Additional context per evaluation can also be passed:
+Pass additional context to one evaluation:
```java
String variation = f.getVariation(featureKey, additionalContext);
@@ -302,7 +304,9 @@ String variation = f.getVariation(featureKey, additionalContext);
## Getting variables
-Your features may also include [variables](https://featurevisor.com/docs/features/#variables), which can be evaluated as follows:
+### Getting feature variables
+
+Evaluate a feature's [variables](https://featurevisor.com/docs/features/#variables):
```java
String variableKey = "bgColor";
@@ -310,7 +314,7 @@ String variableKey = "bgColor";
String bgColorValue = f.getVariableString(featureKey, variableKey);
```
-Additional context per evaluation can also be passed:
+Pass additional context to one evaluation:
```java
String bgColorValue = f.getVariableString(featureKey, variableKey, additionalContext);
@@ -365,7 +369,7 @@ Map> nested = f.getVariableObject(
);
```
-Typed overloads are additive and non-breaking. If decoding fails for the requested target type, these methods return `null`.
+If decoding fails for the requested target type, these methods return `null`.
For dynamic JSON values with unknown shape, use `getVariableJSONNode`:
@@ -385,7 +389,7 @@ If a variable schema type is `json` and the resolved value is a malformed string
- `getVariableJSONNode(...)`
- `getVariableJSON(...)`
-## Getting global variables
+### Getting global variables
Global variables use the same overloaded methods as variables inside features. A call with one key evaluates a global variable, while a call with a feature key and variable key evaluates a variable owned by that feature:
@@ -397,11 +401,35 @@ Evaluation evaluation = f.evaluateVariable("checkoutSettings", context);
The type specific methods are `getVariableBoolean`, `getVariableString`, `getVariableInteger`, `getVariableDouble`, `getVariableArray`, `getVariableObject`, and `getVariableJSON`.
-Global variables resolve sticky values first, then required features, then the first matching override, and finally their default value. If required features are unmet, `disabledValue` is used unless `useDefaultWhenDisabled` is enabled. Caller defaults are only used when the evaluation itself has no value.
+Root conditions and overrides are applied for you. See [Variables](https://featurevisor.com/docs/variables/#global-variables) and [Conditions](https://featurevisor.com/docs/conditions/) for authoring.
+
+### Conditions and composed variables
+
+Conditions combine attribute comparisons, literal segment keys, feature getters and variable comparisons. Arrays imply AND. Logical groups use `and`, `or` and `not`, with NOT negating the AND of its children. Empty expressions are false. Variable paths in generated datafiles are compiled arrays, not authored dotted paths.
+
+Feature evaluation checks sticky values, force rules, root conditions and traffic rules in that order. Global variables check sticky values and root conditions before sources and overrides. An unmet root returns `conditions_unmet` and exposes `Evaluation.getConditions()`. Disabled variations and feature variables retain their existing disabled reasons.
+
+Composed global variables use the ordinary getters:
+
+```java
+Object screen = f.getVariable("checkoutScreen", context);
+Object policy = f.getVariable("policyAlias", context);
+Evaluation evaluation = f.evaluateVariable("checkoutScreen", context);
+```
+
+A map composition assembles a fresh outer object with literal properties from feature flags, variations, feature variables and global variables. A string composition aliases one literal global variable key and returns its value directly. Sources use effective context, child sticky state and modules. False, zero, empty strings, arrays and explicit null remain values. JSON sources decode exactly once, even through alias chains. An outer caller default is never forwarded to sources.
+
+Compositions have no defaultValue or useDefaultWhenDisabled. Root conditions can return disabledValue or null without reading sources or overrides. Whole variable sticky state bypasses composition. Sources are read before runtime overrides, whose mutation operations copy changed paths without modifying sources, siblings or sticky values. Successful composition reports `variable_composed`, or `variable_override_rule` when an override matches.
+
+Treat supplied datafiles and returned objects as read only. Copy values before changing them. Composed members and string aliases retain ordinary getter ownership. Only the map's outer object is freshly assembled.
+
+Conditions, segments and composition share cycle detection and a combined dependency depth bound of 256. A cycle or exhausted depth fails the complete evaluation through its ordinary error result. A thread stack too small for the chain fails the same way, and getters never throw `StackOverflowError`. A subsequent public call starts a fresh session.
-## Getting aggregate evaluations
+To see which overrides changed a value, read `variableOverridePaths` from `evaluateVariable`. The singular override fields describe the final match.
-You can get evaluations of all features available in the SDK instance:
+## Getting evaluations
+
+Evaluate all features available in the instance:
```java
import com.featurevisor.sdk.EvaluatedFeatures;
@@ -414,7 +442,7 @@ Map evaluations = allEvaluations.getValue();
System.out.println(evaluations);
// {
-// "myFeature": {
+// "checkout": {
// "enabled": true,
// "variation": "control",
// "variables": {
@@ -437,24 +465,24 @@ Global variables can be evaluated together as well:
Map variables = f.getVariableEvaluations(context, null, null);
```
-## Sticky features and variables
+## Sticky
-For the lifecycle of the SDK instance in your application, you can set some features with sticky values, meaning that they will not be evaluated against the fetched [datafile](https://featurevisor.com/docs/building-datafiles/):
+Set sticky values to take precedence over feature evaluation against the [datafile](https://featurevisor.com/docs/building-datafiles/):
Sticky values belong to an SDK or child instance. Feature sticky values and global variable sticky values are independent.
```java
Map stickyFeatures = new HashMap<>();
-Map myFeatureSticky = new HashMap<>();
-myFeatureSticky.put("enabled", true);
-myFeatureSticky.put("variation", "treatment");
+Map checkoutSticky = new HashMap<>();
+checkoutSticky.put("enabled", true);
+checkoutSticky.put("variation", "treatment");
-Map myVariables = new HashMap<>();
-myVariables.put("myVariableKey", "myVariableValue");
-myFeatureSticky.put("variables", myVariables);
+Map checkoutVariables = new HashMap<>();
+checkoutVariables.put("myVariableKey", "myVariableValue");
+checkoutSticky.put("variables", checkoutVariables);
-stickyFeatures.put("myFeatureKey", myFeatureSticky);
+stickyFeatures.put("checkout", checkoutSticky);
Map anotherFeatureSticky = new HashMap<>();
anotherFeatureSticky.put("enabled", false);
@@ -468,14 +496,14 @@ Featurevisor f = Featurevisor.createFeaturevisor(new Featurevisor.FeaturevisorOp
Once initialized with sticky features, the SDK will look for values there first before evaluating the targeting conditions and going through the bucketing process.
-You can also set sticky features after the SDK is initialized:
+Set sticky features after initialization:
```java
Map stickyFeatures = new HashMap<>();
// ... build sticky features map
f.setStickyFeatures(stickyFeatures, true); // replace existing sticky features
-f.setStickyVariables(Map.of("welcomeMessage", "Welcome back"), true);
+f.setStickyVariables(Map.of("welcomeMessage", "Welcome back"), true); // replace
```
## Setting datafile
@@ -486,47 +514,40 @@ You may also initialize the SDK without passing `datafile`, and set it later on:
f.setDatafile(datafileContent);
```
+`setDatafile` accepts either a parsed `DatafileContent` or a raw JSON string. All overloads return `true` when the datafile is accepted, including unchanged content, and `false` when the input is rejected or the instance is closed. Rejected input leaves the last accepted datafile in place and reports the existing diagnostic and error event, without a `datafile_set` event. Calls after closing do nothing. Accepted calls still emit `datafile_set`, even when nothing changed. The return value reports acceptance, not whether any features changed.
+
+```java
+boolean accepted = f.setDatafile(datafileContent);
+```
+
### Merging by default
By default, `setDatafile(datafile)` merges the incoming datafile with the SDK's stored datafile. Incoming top level metadata is used, and incoming segments, features, and global variables override existing entities with the same keys.
-This means you can call `setDatafile` more than once with different datafiles, and the SDK instance accumulates their features and segments together. This is what makes [loading datafiles on demand](#loading-datafiles-on-demand) possible.
+Call `setDatafile` with different datafiles to merge their features, segments, and global variables. Use this to [load datafiles on demand](#loading-datafiles-on-demand).
### Replacing
To replace the stored datafile entirely, pass `true`:
```java
-f.setDatafile(datafileContent, true);
+f.setDatafile(datafileContent, true); // replace
```
### Loading datafiles on demand
-Because merging is the default, a single SDK instance can start with a small datafile and load more datafiles later as your application needs them, instead of downloading every feature upfront.
-
-This pairs well with [targets](https://featurevisor.com/docs/targets/), where each target produces a smaller datafile for a specific part of your application:
+Merge datafiles for different [targets](https://featurevisor.com/docs/targets/) into the same instance. Here, the two datafile values have already been loaded and validated by your application from the same published release:
```java
-Featurevisor f = Featurevisor.createFeaturevisor(new Featurevisor.FeaturevisorOptions());
-
-void loadDatafile(String target) throws Exception {
- String url = "https://cdn.yoursite.com/production/featurevisor-" + target + ".json";
- String json = fetchJson(url); // use your HTTP client of choice
- DatafileContent datafile = DatafileContent.fromJson(json);
-
- // merges into whatever was loaded before
- f.setDatafile(datafile);
-}
-
-loadDatafile("products");
-
-// later, when the user reaches checkout
-loadDatafile("checkout");
+f.setDatafile(productsDatafile);
+f.setDatafile(checkoutDatafile);
```
+Use immutable release URLs, check HTTP responses and keep shared dependencies consistent. See [Loading datafiles on demand](https://featurevisor.com/docs/use-cases/on-demand-datafiles/) for the complete loading and refresh flow.
+
### Updating datafile
-You can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile-set) event that you can listen and react to accordingly.
+Each accepted datafile update emits a [`datafile_set`](#datafile-set) event. Subscribe to react to updates.
The triggers for setting the datafile again can be:
@@ -537,21 +558,42 @@ The triggers for setting the datafile again can be:
### Interval-based update
-Here's an example of using interval-based update:
+Replace the complete datafile on each refresh so definitions removed upstream are removed from the SDK too. Use merging only when loading partial datafiles intentionally.
```java
-// Using ScheduledExecutorService for periodic updates
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import com.featurevisor.sdk.DatafileContent;
+
+// f is the existing Featurevisor instance
+HttpClient client = HttpClient.newHttpClient();
+HttpRequest request = HttpRequest.newBuilder(URI.create(datafileUrl))
+ .timeout(Duration.ofSeconds(30))
+ .build();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
- // Fetch new datafile content
- String newDatafileContent = // ... fetch from your CDN
- DatafileContent newDatafile = DatafileContent.fromJson(newDatafileContent);
-
- // Merge into the SDK's existing datafile
- f.setDatafile(newDatafile);
+ try {
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() == 200) {
+ DatafileContent newDatafile = DatafileContent.fromJson(response.body());
+ f.setDatafile(newDatafile, true); // replace
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (Exception e) {
+ System.err.println("Failed to update datafile: " + e.getMessage());
+ }
}, 0, 5, TimeUnit.MINUTES);
```
+Call `scheduler.shutdownNow()` when your application shuts down.
+
## Diagnostics
By default, Featurevisor reports diagnostics to the console for `info` level and above with a `[Featurevisor]` prefix.
@@ -582,16 +624,19 @@ Featurevisor f = Featurevisor.createFeaturevisor(new Featurevisor.FeaturevisorOp
}));
```
-Every diagnostic has `level`, `code`, `message`, and an object-shaped `details` map. Optional `module`, `moduleName`, and `originalError` fields describe provenance. Evaluation metadata belongs in `details`.
+Generated datafiles use schemaVersion `"4"`. Schema `"2"` selectors normalize eagerly when accepted, while schema `"4"` entities normalize on first read. Partial updates invalidate only incoming entities. Replacement clears all entity caches. Legacy compatibility conversion does not change caller objects or the informational version metadata.
-Diagnostic handlers are isolated from SDK behaviour. An exception in a handler does not stop other handlers or evaluations.
+Malformed stringified conditions fail only their complete selector, including through NOT or segment aliases, without rejecting the datafile. `conditions_parse_error` is reported at error level once per selector per incoming entity load. Its details are exactly `conditions`, `entityType` and `entityKey`. Eager diagnostics run after storage, and lazy diagnostics run after caching, so callbacks may safely reenter getters.
+
+Every diagnostic has `level`, `code`, `message`, and an object shaped `details` map. Optional `module`, `moduleName`, and `originalError` fields describe provenance. Evaluation metadata belongs in `details`.
+Diagnostic handlers are isolated from SDK behaviour. An exception in a handler does not stop other handlers or evaluations.
## Events
-Featurevisor SDK implements a simple event emitter that allows you to listen to events that happen in the runtime.
+Subscribe to SDK events to respond to context, sticky state and datafile changes.
-You can listen to these events that can occur at various stages in your application:
+Subscribe to instance lifecycle events:
### `datafile_set`
@@ -616,7 +661,7 @@ FeaturevisorUnsubscribe unsubscribe = f.on(FeaturevisorEventName.DATAFILE_SET, (
unsubscribe.unsubscribe();
```
-The `features` and `variables` arrays contain directly changed entities and entities affected through segment or required feature dependencies.
+The `features` and `variables` arrays contain directly changed entities and their transitive dependants through conditions, segments and composition, including removed dependencies.
- added, or
- updated, or
@@ -670,7 +715,7 @@ The `error` event is emitted for diagnostics whose level is `ERROR`.
## Evaluation details
-Besides logging with debug level enabled, you can also get more details about how the feature variations and variables are evaluated in the runtime against given context:
+Inspect how variations and variables are evaluated against a context:
```java
// flag
@@ -701,15 +746,24 @@ And optionally these properties depending on whether you are evaluating a featur
- `variationValue`: the variation value
- `variableKey`: the variable key
- `variableValue`: the variable value
-- `variableSchema`: the variable schema
+- `variableSchema`: the feature variable schema
+- `variable`: the global variable definition
+- `conditions`: the unmet root expression
+- `variableOverrideIndex`, `variableOverrideKey`, `variableOverridePath`, `variableOverridePaths`: matched override provenance
## Modules
+The before evaluation and after evaluation phases each use the module membership at the start of that phase. Where an evaluation uses bucketing, the bucket key and bucket value phases each use their own starting membership. Adding or removing a module during a phase does not change the iteration already in progress. The next phase uses the membership then installed. A module added before the after phase can receive after callbacks for evaluations already in progress, even if it did not receive their before callbacks. A module removed before the after phase receives no after callback.
+
Modules allow you to intercept the evaluation process and customize it further as per your needs.
-For feature evaluations, all `before` callbacks run in registration order, followed by all `beforeEvaluation` callbacks. After evaluation and caller defaults, all `afterEvaluation` callbacks run, followed by all `after` callbacks. Global variable evaluations use only `beforeEvaluation` and `afterEvaluation`. Required feature checks run through the complete module pipeline, and transformed defaults are preserved.
+`beforeEvaluation` and `afterEvaluation` run in registration order for feature and global variable evaluations. Condition and composition reads use the same complete module pipeline, and transformed defaults are preserved. One pair surrounds a complete accumulating evaluation, not each mutation operation.
+
+Module options expose `getDependencyOf()` for an internal getter read. It identifies the immediate requester by type and optional feature and variable keys. `getDependencyPurpose()` is `condition` for eligibility checks or `composition` for consumed values. Application calls, including an independent public getter invoked from a module, have neither marker. Repeated equivalent internal reads can be memoized within one public call, separately for each purpose and requester.
-`before` and `after` remain available as deprecated feature-only compatibility callbacks. Use `beforeEvaluation` and `afterEvaluation` for new modules so the same callbacks can handle feature and global variable evaluations.
+Treat sticky maps and their nested values as read only after passing them to an instance or module. Use sticky setters or return replacement maps from a module to change them. Mutating an existing map in place is not supported.
+
+Modules should return the same result for the same input during one evaluation. Pass values that change through context.
### Defining a module
@@ -724,15 +778,12 @@ FeaturevisorModule myCustomModule = new FeaturevisorModule("my-custom-module")
})
// before evaluation
- .before(options -> {
+ .beforeEvaluation(options -> {
Map context = new HashMap<>(options.getContext());
context.put("someAdditionalAttribute", "value");
return options.copy().context(context);
})
- // before any feature or global variable evaluation
- .beforeEvaluation(options -> options)
-
// configure bucket key
.bucketKey(options -> {
String bucketKey = options.getBucketKey();
@@ -746,9 +797,6 @@ FeaturevisorModule myCustomModule = new FeaturevisorModule("my-custom-module")
})
// after evaluation
- .after((evaluation, options) -> evaluation)
-
- // after any feature or global variable evaluation
.afterEvaluation((evaluation, options) -> evaluation)
// called by f.close()
@@ -759,7 +807,7 @@ FeaturevisorModule myCustomModule = new FeaturevisorModule("my-custom-module")
### Registering modules
-You can register modules at the time of SDK initialization:
+Register modules when you create the instance:
```java
List modules = new ArrayList<>();
@@ -813,12 +861,12 @@ childContext.put("userId", "123");
ChildInstance childF = f.spawn(childContext);
```
-Now you can pass the child instance where your individual request is being handled, and you can continue to evaluate features targeting that specific user alone:
+Pass the child instance to the request handler to evaluate features for that user:
```java
-boolean isEnabled = childF.isEnabled("my_feature");
-String variation = childF.getVariation("my_feature");
-String variableValue = childF.getVariableString("my_feature", "my_variable");
+boolean isEnabled = childF.isEnabled("checkout");
+String variation = childF.getVariation("checkout");
+String variableValue = childF.getVariableString("checkout", "bgColor");
```
Similar to parent SDK, child instances also support several additional methods:
@@ -853,44 +901,6 @@ Both primary and child instances support a `.close()` method, that removes forgo
f.close();
```
-## CLI usage
-
-This package also provides a CLI tool for running your Featurevisor project's test specs and benchmarking against this Java SDK:
-
-All three commands accept repeatable `--target=` options. `test` builds only the selected Target datafiles and runs untargeted assertions plus assertions for those targets. `benchmark` and `assess-distribution` run independently against every selected Target datafile. Without `--target`, existing project-wide behaviour is preserved. Project definitions, test specs, Target discovery, and datafile generation continue to come from the Node.js CLI.
-
-### Test
-
-Learn more about testing [here](https://featurevisor.com/docs/testing/).
-
-```bash
-$ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="test --projectDirectoryPath=/absolute/path/to/your/featurevisor/project"
-```
-
-Additional options that are available:
-
-```bash
-$ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="test --projectDirectoryPath=/absolute/path/to/your/featurevisor/project --quiet --onlyFailures --keyPattern=myFeatureKey --assertionPattern=#1 --showDatafile --inflate=1"
-```
-
-The test runner builds base datafiles and Target datafiles, then uses a Target datafile when an assertion contains `target`.
-
-### Benchmark
-
-Learn more about benchmarking [here](https://featurevisor.com/docs/cli/#benchmarking).
-
-```bash
-$ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="benchmark --projectDirectoryPath=/absolute/path/to/your/featurevisor/project --environment=production --feature=myFeatureKey --context='{\"country\": \"nl\"}' --n=1000"
-```
-
-### Assess distribution
-
-Learn more about assessing distribution [here](https://featurevisor.com/docs/cli/#assess-distribution).
-
-```bash
-$ mvn exec:java -Dexec.mainClass="com.featurevisor.cli.CLI" -Dexec.args="assess-distribution --projectDirectoryPath=/absolute/path/to/your/featurevisor/project --environment=production --feature=foo --variation --context='{\"country\": \"nl\"}' --populateUuid=userId --populateUuid=deviceId --n=1000"
-```
-
## OpenFeature
The OpenFeature provider is published as a separate artifact. Applications that use only `com.featurevisor:featurevisor-java` receive no provider classes or OpenFeature dependency.
@@ -901,13 +911,11 @@ Add the provider with the same version as the Featurevisor Java SDK:
com.featurevisor
featurevisor-openfeature
- 4.1.0
+ 5.0.0
```
-The provider artifact depends on the matching Featurevisor Java SDK and the compatible official OpenFeature SDK, so no additional dependency is required.
-
-When upgrading from an earlier release, replace the direct `dev.openfeature:sdk` dependency with `com.featurevisor:featurevisor-openfeature`. The provider's Java package and public API are unchanged.
+The provider artifact depends on the matching Featurevisor Java SDK and the official OpenFeature Java SDK 1.23.0, so no additional dependency is required. Both retain Java 11 as the minimum runtime.
```java
import com.featurevisor.openfeature.FeaturevisorOpenFeatureProvider;
@@ -930,7 +938,7 @@ Use `checkout` for a flag, `checkout:variation` for its variation, `checkout:tit
OpenFeature's targeting key maps to `userId` by default. `targetingKeyField`, `keySeparator`, `variationKey`, and `globalVariablePrefix` on `FeaturevisorOpenFeatureProvider.Options` can customize the mapping. The global variable prefix defaults to `variable` and cannot contain the configured separator.
-You can also reuse an existing Featurevisor instance:
+Reuse an existing Featurevisor instance:
```java
Featurevisor featurevisor = Featurevisor.createFeaturevisor(featurevisorOptions);
@@ -939,10 +947,92 @@ var provider = new FeaturevisorOpenFeatureProvider(featurevisor);
The caller owns an instance passed this way. Provider shutdown does not close it. Call `featurevisor.close()` when every consumer is finished with it. When the provider creates the instance from options, the provider owns and closes it. If both are configured, the existing instance takes precedence.
+An invalid initial datafile in an owned instance fails `setProviderAndWait` with a parse error, independently of the diagnostic log level. Valid updates clear the error and emit readiness. Supplied diagnostic callbacks and module setup remain unchanged, and shutdown closes only an owned instance.
+
+Composed values map to `TARGETING_MATCH` without additional JSON decoding. Unmet root conditions map to `DISABLED`. Ordinary null values retain the provider default policy.
+
See the [OpenFeature provider guide](https://featurevisor.com/docs/sdks/openfeature/) for resolution reasons, errors, metadata, tracking, lifecycle, and providers for other languages.
+## CLI usage
+
+Compile the CLI module from this SDK repository:
+
+```bash
+mvn -pl featurevisor-sdk compile
+```
+
+The native test runner requires `@featurevisor/cli` 4.0.0 or later and Node.js 24 or later. Install the Node.js CLI in your Featurevisor project:
+
+```bash
+npm install --save-dev @featurevisor/cli@^4.0.0
+```
+
+Project parsing and datafile generation use `npx featurevisor`. Evaluations run through this Java SDK. The SDK runtime has no Node.js dependency. All three commands accept repeated `--target=` options.
+
+Use the Node.js CLI directly for authoring commands such as `init`, `lint`, `build` and `list`. Native commands delegate project parsing, test expansion and datafile generation to Node.js, then evaluate the results with this SDK.
+
+Options use kebab case, such as `--project-directory-path`. The camelCase forms from earlier releases still work, and `--root-directory-path` is an alias for `--project-directory-path`. Pass values as `--name=value` or `--name value`. Repeat `--target` to select several targets in order. Run ` --help` to list every option.
+
+`--n` and `-n` accept both value forms. Repeat `--populate-uuid` for multiple context attributes. Quote JSON and paths containing spaces.
+
+### Test
+
+```bash
+mvn -q -pl featurevisor-sdk exec:java -Dexec.mainClass=com.featurevisor.cli.CLI \
+ -Dexec.args="test --project-directory-path=/path/to/featurevisor-project --only-failures"
+```
+
+See [Testing](https://featurevisor.com/docs/testing/) for authoring specs and [Checks](https://featurevisor.com/docs/testing/#checks) for check syntax and operators.
+
+Segment specs and checks evaluate ordinary SDK gates with the selected datafile, context, fixed bucket and isolated sticky state. Compiled execution definitions restore missing segments. Existing Target segment specializations remain authoritative. Attribute only segment chains can omit the environment, while segments depending on features or variables require the selected datafile. All transitive root and child check references are preflighted before getters. Synthetic segment gates do not count towards coverage.
+
+Useful test options:
+
+- `--key-pattern=` and `--assertion-pattern=` filter tests.
+- `--entity-type=scenario` selects scenarios. `--set=` selects one project set. Omitting `--set` runs all sets.
+- `--case=` reruns one expanded case. Copy the opaque ID or rerun command from a failure report.
+- `--json --pretty` produces an indented JSON report.
+- `--coverage` reports observed rule and override selections. `--uncovered` lists features and global variables without associated specs.
+- `--only-failures`, `--quiet`, `--verbose` and `--show-datafile` control diagnostic output.
+
+JSON rerun arguments use `argumentEncoding: maven-exec`. Pass the shell quoted native argument list as one `-Dexec.args` value, or copy the printed rerun command.
+
+### Benchmark
+
+The examples below use a project without environments. Add `--environment=` if your project configures environments. In a sets project, omit `--set` to run every set or pass `--set=`. Repeat `--target=` to evaluate specific target datafiles. The Node.js CLI validates these options and builds the datafiles. Java evaluates them. Replace `checkout` with a feature defined in your project.
+
+```bash
+mvn -q -pl featurevisor-sdk exec:java -Dexec.mainClass=com.featurevisor.cli.CLI \
+ -Dexec.args="benchmark --project-directory-path=/path/to/featurevisor-project --feature=checkout --context='{\"country\":\"nl\"}' --n=1000"
+```
+
+Use `--variation` for a variation or `--variable=` for a feature variable. Omit `--feature` to benchmark a global variable. Do not combine `--variation` with `--variable`. See [Benchmarking](https://featurevisor.com/docs/cli/#benchmarking).
+
+### Assess distribution
+
+```bash
+mvn -q -pl featurevisor-sdk exec:java -Dexec.mainClass=com.featurevisor.cli.CLI \
+ -Dexec.args="assess-distribution --project-directory-path=/path/to/featurevisor-project --feature=checkout --populate-uuid=userId --n=1000"
+```
+
+The Java CLI also accepts `--variation` or `--variable=` to assess a variation or feature variable instead of the enabled state. These selectors are specific to this runner and are not supported by the Node.js `assess-distribution` command. Repeat `--populate-uuid=` to generate UUID values for multiple context attributes. See [Assess distribution](https://featurevisor.com/docs/cli/#assess-distribution).
+
+## GitHub repositories
+
+- SDK: [featurevisor/featurevisor-java](https://github.com/featurevisor/featurevisor-java)
+- Example application: [featurevisor/featurevisor-example-java](https://github.com/featurevisor/featurevisor-example-java)
+- Kotlin Android example application: [featurevisor/featurevisor-example-android](https://github.com/featurevisor/featurevisor-example-android)
+
+
+
+The aggregate evaluation reference has moved to [Getting evaluations](#getting-evaluations).
+
+
+
+The sticky state reference has moved to [Sticky](#sticky).
+
## Development of this package
### Setting up
@@ -959,6 +1049,27 @@ $ mvn install
$ mvn test
```
+Compile the actual initialization snippet for Java 11 and execute it with an isolated local datafile:
+
+```bash
+mvn -Dmaven.test.skip=true package
+bash scripts/test-readme-initialization.sh
+```
+
+The README check also covers missing files and invalid JSON. CI runs it on both configured JDKs. Pass `--offline` to reuse cached Maven dependencies locally.
+
+To check runtime command selection against an already built Node.js monorepo:
+
+```bash
+mvn -pl featurevisor-sdk test-compile dependency:build-classpath -Dmdep.outputFile=target/runtime-classpath.txt
+node scripts/test-runtime-commands.mjs ../featurevisor java \
+ -cp "featurevisor-sdk/target/classes:$(cat featurevisor-sdk/target/runtime-classpath.txt)" \
+ com.featurevisor.cli.CLI
+bash scripts/test-release.sh 5.0.0
+```
+
+These checks create temporary flat, environment and sets projects without changing the monorepo or downloading packages. The classpath command above uses the POSIX `:` separator.
+
The repository is a Maven reactor with two published libraries:
- `com.featurevisor:featurevisor-java`
@@ -972,8 +1083,10 @@ $ make verify-artifacts
### Releasing
+The SDK, provider and parent POM share version `5.0.0`. Before tagging, run `bash scripts/verify-release.sh v5.0.0` to check the Maven revision, CLI and both README installation examples.
+
1. Merge the release changes into `main`.
-2. Tag the release with a `v` prefix, such as `v4.1.0`, and push the tag.
+2. Tag the release with a `v` prefix, such as `vX.Y.Z`, and push the tag.
3. GitHub Actions verifies and publishes the parent POM, Java SDK, and OpenFeature provider to [GitHub Packages](https://github.com/orgs/featurevisor/packages?repo_name=featurevisor-java).
4. Create the corresponding [GitHub release](https://github.com/featurevisor/featurevisor-java/releases).
diff --git a/conformance/openfeature-provider.json b/conformance/openfeature-provider.json
new file mode 100644
index 0000000..ef8c834
--- /dev/null
+++ b/conformance/openfeature-provider.json
@@ -0,0 +1,264 @@
+{
+ "kind": "featurevisor.openfeature-provider",
+ "schemaVersion": 1,
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "provider-conformance",
+ "segments": {},
+ "features": {
+ "checkout": {
+ "bucketBy": "userId",
+ "variations": [
+ { "value": "on", "variables": { "config": "{\"enabled\":true}", "nullable": "null" } }
+ ],
+ "variablesSchema": {
+ "config": { "type": "json", "defaultValue": "{}" },
+ "nullable": { "type": "json", "defaultValue": "null" }
+ },
+ "force": [
+ {
+ "conditions": { "attribute": "userId", "operator": "equals", "value": "forced" },
+ "enabled": true,
+ "variation": "on"
+ }
+ ],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000, "variation": "on" }]
+ },
+ "disabled": {
+ "bucketBy": "userId",
+ "variations": [],
+ "force": [
+ {
+ "conditions": { "attribute": "blocked", "operator": "equals", "value": true },
+ "enabled": false
+ }
+ ],
+ "traffic": []
+ },
+ "accountTargeted": {
+ "bucketBy": "accountId",
+ "variations": [],
+ "force": [
+ {
+ "conditions": { "attribute": "accountId", "operator": "equals", "value": "" },
+ "enabled": true
+ }
+ ],
+ "traffic": []
+ },
+ "targetOnly": {
+ "bucketBy": "userId",
+ "variations": [],
+ "force": [
+ {
+ "conditions": { "attribute": "userId", "operator": "equals", "value": "forced" },
+ "enabled": true
+ }
+ ],
+ "traffic": []
+ },
+ "conditional": {
+ "bucketBy": "userId",
+ "conditions": { "feature": "checkout", "enabled": false },
+ "traffic": [{ "key": "all", "conditions": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "settings": { "type": "json", "defaultValue": "{\"region\":\"eu\"}" },
+ "nullable": { "type": "json", "defaultValue": "null" },
+ "settingsAlias": { "type": "json", "compose": "settings" },
+ "jsonText": { "type": "json", "defaultValue": "\"null\"" },
+ "jsonTextAlias": { "type": "json", "compose": "jsonText" },
+ "screen": {
+ "type": "object",
+ "compose": [
+ { "key": "enabled", "feature": "checkout" },
+ { "key": "settings", "variable": "settings" }
+ ]
+ },
+ "restricted": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": { "attribute": "allowed", "operator": "equals", "value": true }
+ },
+ "screenOverride": {
+ "type": "object",
+ "compose": "screen",
+ "overrideProgram": [
+ {
+ "key": "regional",
+ "conditions": "*",
+ "operations": [{ "op": "set", "path": ["settings", "region"], "value": "us" }],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ },
+ "brokenAlias": { "type": "object", "compose": "brokenAlias" }
+ }
+ },
+ "cases": [
+ {
+ "name": "forced flag",
+ "flagKey": "checkout",
+ "type": "boolean",
+ "default": false,
+ "context": { "targetingKey": "forced" },
+ "expected": { "value": true, "reason": "TARGETING_MATCH" }
+ },
+ {
+ "name": "variation selector",
+ "flagKey": "checkout:variation",
+ "type": "string",
+ "default": "fallback",
+ "context": { "targetingKey": "forced" },
+ "expected": { "value": "on", "reason": "TARGETING_MATCH", "variant": "on" }
+ },
+ {
+ "name": "feature JSON",
+ "flagKey": "checkout:config",
+ "type": "object",
+ "default": {},
+ "context": { "targetingKey": "forced" },
+ "expected": { "value": { "enabled": true } }
+ },
+ {
+ "name": "global JSON",
+ "flagKey": "variable:settings",
+ "type": "object",
+ "default": {},
+ "context": {},
+ "expected": { "value": { "region": "eu" }, "reason": "DEFAULT" }
+ },
+ {
+ "name": "feature JSON null",
+ "flagKey": "checkout:nullable",
+ "type": "object",
+ "default": { "fallback": true },
+ "context": { "targetingKey": "forced" },
+ "expected": { "value": { "fallback": true } }
+ },
+ {
+ "name": "global JSON null",
+ "flagKey": "variable:nullable",
+ "type": "object",
+ "default": { "fallback": true },
+ "context": {},
+ "expected": { "value": { "fallback": true }, "reason": "DEFAULT" }
+ },
+ {
+ "name": "disabled flag",
+ "flagKey": "disabled",
+ "type": "boolean",
+ "default": true,
+ "context": { "blocked": true },
+ "expected": { "value": false, "reason": "TARGETING_MATCH" }
+ },
+ {
+ "name": "missing flag",
+ "flagKey": "missing",
+ "type": "boolean",
+ "default": false,
+ "context": {},
+ "expected": { "value": false, "reason": "ERROR", "errorCode": "FLAG_NOT_FOUND" }
+ },
+ {
+ "name": "type mismatch",
+ "flagKey": "checkout:config",
+ "type": "string",
+ "default": "fallback",
+ "context": { "targetingKey": "forced" },
+ "expected": { "value": "fallback", "reason": "ERROR", "errorCode": "TYPE_MISMATCH" }
+ },
+ {
+ "name": "composed object",
+ "flagKey": "variable:screen",
+ "type": "object",
+ "default": {},
+ "context": { "targetingKey": "forced" },
+ "expected": {
+ "value": { "enabled": true, "settings": { "region": "eu" } },
+ "reason": "TARGETING_MATCH"
+ }
+ },
+ {
+ "name": "string alias to JSON object",
+ "flagKey": "variable:settingsAlias",
+ "type": "object",
+ "default": {},
+ "context": {},
+ "expected": { "value": { "region": "eu" }, "reason": "TARGETING_MATCH" }
+ },
+ {
+ "name": "string alias decodes JSON exactly once",
+ "flagKey": "variable:jsonTextAlias",
+ "type": "string",
+ "default": "fallback",
+ "context": {},
+ "expected": { "value": "null", "reason": "TARGETING_MATCH" }
+ },
+ {
+ "name": "composed override",
+ "flagKey": "variable:screenOverride",
+ "type": "object",
+ "default": {},
+ "context": { "targetingKey": "forced" },
+ "expected": {
+ "value": { "enabled": true, "settings": { "region": "us" } },
+ "reason": "TARGETING_MATCH"
+ }
+ },
+ {
+ "name": "unmet variable root conditions",
+ "flagKey": "variable:restricted",
+ "type": "boolean",
+ "default": true,
+ "context": {},
+ "expected": { "value": false, "reason": "DISABLED" }
+ },
+ {
+ "name": "unmet feature root conditions",
+ "flagKey": "conditional",
+ "type": "boolean",
+ "default": true,
+ "context": { "targetingKey": "forced" },
+ "expected": { "value": false, "reason": "DISABLED" }
+ },
+ {
+ "name": "composition dependency error",
+ "flagKey": "variable:brokenAlias",
+ "type": "object",
+ "default": { "fallback": true },
+ "context": {},
+ "expected": { "value": { "fallback": true }, "reason": "ERROR", "errorCode": "GENERAL" }
+ }
+ ],
+ "targetingCases": [
+ {
+ "name": "absent preserves field",
+ "context": { "userId": "forced" },
+ "expectedUserId": "forced",
+ "expectedValue": true
+ },
+ {
+ "name": "empty overrides field",
+ "context": { "targetingKey": "", "userId": "forced" },
+ "expectedUserId": "",
+ "expectedValue": false
+ },
+ {
+ "name": "nonempty overrides field",
+ "context": { "targetingKey": "other", "userId": "forced" },
+ "expectedUserId": "other",
+ "expectedValue": false
+ }
+ ],
+ "customTargetingCase": {
+ "flagKey": "accountTargeted",
+ "context": { "targetingKey": "", "accountId": "old" },
+ "targetingKeyField": "accountId",
+ "expectedValue": true,
+ "expectedReason": "TARGETING_MATCH"
+ }
+}
diff --git a/conformance/sdk-composition.json b/conformance/sdk-composition.json
new file mode 100644
index 0000000..7cf7b7f
--- /dev/null
+++ b/conformance/sdk-composition.json
@@ -0,0 +1,436 @@
+{
+ "version": 2,
+ "capability": "composition-and-overrides",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "composition-conformance",
+ "segments": {},
+ "features": {
+ "checkout": {
+ "bucketBy": "userId",
+ "disabledVariationValue": "off",
+ "variations": [{ "value": "control" }],
+ "variablesSchema": {
+ "label": { "type": "string", "defaultValue": "Buy", "disabledValue": "Closed" }
+ },
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "*",
+ "percentage": 100000,
+ "allocation": [{ "range": [0, 100000], "variation": "control" }]
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": { "type": "object", "defaultValue": { "limit": 50, "methods": ["card"] } },
+ "raw": { "type": "json", "defaultValue": "{\"decoded\":true}" },
+ "gate": { "type": "boolean", "defaultValue": true },
+ "policyAlias": {
+ "type": "object",
+ "compose": "policy",
+ "overrideProgram": [
+ {
+ "key": "regional",
+ "keyPath": ["regional"],
+ "conditions": { "attribute": "country", "operator": "equals", "value": "nl" },
+ "operations": [{ "op": "set", "path": ["limit"], "value": 80 }],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ },
+ "policyChain": { "type": "object", "compose": "policyAlias" },
+ "booleanAlias": { "type": "boolean", "compose": "gate" },
+ "numberSource": { "type": "integer", "defaultValue": 0 },
+ "numberAlias": { "type": "integer", "compose": "numberSource" },
+ "stringSource": { "type": "string", "defaultValue": "" },
+ "stringAlias": { "type": "string", "compose": "stringSource" },
+ "jsonText": { "type": "json", "defaultValue": "\"null\"" },
+ "jsonAlias": { "type": "json", "compose": "jsonText" },
+ "jsonChain": { "type": "json", "compose": "jsonAlias" },
+ "arraySource": { "type": "array", "defaultValue": ["card"] },
+ "arrayAlias": {
+ "type": "array",
+ "compose": "arraySource",
+ "overrideProgram": [
+ {
+ "key": "extra",
+ "keyPath": ["extra"],
+ "segments": "*",
+ "operations": [{ "op": "appendUnique", "path": [], "value": "ideal" }],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ },
+ "screenAlias": { "type": "object", "compose": "screen" },
+ "aliasMembers": {
+ "type": "object",
+ "compose": [
+ { "key": "policy", "variable": "policyChain" },
+ { "key": "raw", "variable": "jsonChain" }
+ ]
+ },
+ "aliasCycle": { "type": "object", "compose": "aliasCycle" },
+ "missingAlias": { "type": "object", "compose": "missingSource" },
+ "screen": {
+ "type": "object",
+ "compose": [
+ { "key": "enabled", "feature": "checkout" },
+ { "key": "label", "feature": "checkout", "variable": "label" },
+ { "key": "policy", "variable": "policy" },
+ { "key": "raw", "variable": "raw" },
+ { "key": "variation", "feature": "checkout", "variation": true }
+ ],
+ "overrideProgram": [
+ {
+ "key": "netherlands",
+ "keyPath": ["netherlands"],
+ "conditions": { "attribute": "country", "operator": "equals", "value": "nl" },
+ "operations": [{ "op": "set", "path": ["policy", "limit"], "value": 90 }],
+ "onMatch": 1,
+ "onMiss": 2
+ },
+ {
+ "key": "amsterdam",
+ "keyPath": ["netherlands", "amsterdam"],
+ "conditions": { "attribute": "city", "operator": "equals", "value": "Amsterdam" },
+ "operations": [{ "op": "set", "path": ["label"], "value": "Collect" }],
+ "onMatch": 2,
+ "onMiss": 2
+ },
+ {
+ "key": "eligible",
+ "keyPath": ["eligible"],
+ "requiredFeatures": ["checkout"],
+ "conditions": {"and":[{"attribute":"country","operator":"equals","value":"nl"},{"variable":"gate","operator":"equals","value":true}]},
+ "operations": [{ "op": "set", "path": ["policy", "limit"], "value": 100 }],
+ "onMatch": 3,
+ "onMiss": 3
+ }
+ ]
+ },
+ "nested": { "type": "object", "compose": [{ "key": "screen", "variable": "screen" }] },
+ "replacement": {
+ "type": "object",
+ "compose": [
+ { "key": "enabled", "feature": "checkout" },
+ { "key": "label", "feature": "checkout", "variable": "label" },
+ { "key": "policy", "variable": "policy" }
+ ],
+ "overrideProgram": [
+ {
+ "key": "literal",
+ "keyPath": ["literal"],
+ "segments": "*",
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "enabled": false,
+ "label": null,
+ "policy": { "limit": 25, "methods": ["cash"] }
+ }
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ },
+ "cycle": { "type": "object", "compose": [{ "key": "self", "variable": "cycle" }] },
+ "invalidProgram": {
+ "type": "object",
+ "compose": [{ "key": "policy", "variable": "policy" }],
+ "overrideProgram": [
+ {
+ "key": "bad",
+ "segments": "*",
+ "operations": [{ "op": "set", "path": ["policy", "limit"], "value": 1 }],
+ "onMatch": 0,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "cases": [
+ {
+ "name": "string-composition-reads-overridden-map",
+ "variable": "screenAlias",
+ "context": { "country": "nl", "city": "Amsterdam" },
+ "expectedValue": {
+ "enabled": true,
+ "label": "Collect",
+ "policy": { "limit": 100, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "control"
+ },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "map-composition-reads-final-string-sources",
+ "variable": "aliasMembers",
+ "context": { "country": "nl" },
+ "expectedValue": { "policy": { "limit": 80, "methods": ["card"] }, "raw": "null" },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-preserves-source-object",
+ "variable": "policyAlias",
+ "context": {},
+ "expectedValue": { "limit": 50, "methods": ["card"] },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-mutates-copy-of-source",
+ "variable": "policyAlias",
+ "context": { "country": "nl" },
+ "expectedValue": { "limit": 80, "methods": ["card"] },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [["regional"]]
+ }
+ },
+ {
+ "name": "string-composition-chain-uses-final-overrides",
+ "variable": "policyChain",
+ "context": { "country": "nl" },
+ "expectedValue": { "limit": 80, "methods": ["card"] },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-preserves-false",
+ "variable": "booleanAlias",
+ "context": {},
+ "stickyVariables": { "gate": false },
+ "expectedValue": false,
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-preserves-zero",
+ "variable": "numberAlias",
+ "context": {},
+ "expectedValue": 0,
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-preserves-empty-string",
+ "variable": "stringAlias",
+ "context": {},
+ "expectedValue": "",
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-json-is-decoded-once",
+ "variable": "jsonChain",
+ "context": {},
+ "expectedValue": "null",
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-preserves-source-null",
+ "variable": "policyAlias",
+ "context": { "country": "nl" },
+ "stickyVariables": { "policy": null },
+ "expectedValue": null,
+ "expectedEvaluation": { "reason": "variable_override_rule" }
+ },
+ {
+ "name": "string-composition-array-root-operation",
+ "variable": "arrayAlias",
+ "context": {},
+ "expectedValue": ["card", "ideal"],
+ "expectedEvaluation": { "reason": "variable_override_rule" }
+ },
+ {
+ "name": "string-composition-sticky-bypasses-source",
+ "variable": "policyChain",
+ "context": {},
+ "stickyVariables": { "policyChain": null },
+ "expectedValue": null,
+ "expectedEvaluation": { "reason": "sticky" }
+ },
+ {
+ "name": "string-composition-missing-source-is-null",
+ "variable": "missingAlias",
+ "context": {},
+ "expectedValue": null,
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "string-composition-cycle-fails-completely",
+ "variable": "aliasCycle",
+ "context": {},
+ "expectedValue": null,
+ "expectedEvaluation": { "reason": "error" }
+ },
+ {
+ "name": "unmatched-assembled-value",
+ "variable": "screen",
+ "context": { "userId": "example-user", "country": "us" },
+ "expectedValue": {
+ "enabled": true,
+ "label": "Buy",
+ "policy": { "limit": 50, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "control"
+ },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "nested-and-continuing-overrides",
+ "variable": "screen",
+ "context": { "userId": "example-user", "country": "nl", "city": "Amsterdam" },
+ "expectedValue": {
+ "enabled": true,
+ "label": "Collect",
+ "policy": { "limit": 100, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "control"
+ },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "eligible",
+ "variableOverridePath": ["eligible"],
+ "variableOverridePaths": [["netherlands"], ["netherlands", "amsterdam"], ["eligible"]]
+ }
+ },
+ {
+ "name": "child-miss-continues-parent",
+ "variable": "screen",
+ "context": { "country": "nl", "city": "Rotterdam" },
+ "expectedValue": {
+ "enabled": true,
+ "label": "Buy",
+ "policy": { "limit": 100, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "control"
+ },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [["netherlands"], ["eligible"]]
+ }
+ },
+ {
+ "name": "disabled-feature-source-and-prerequisite",
+ "variable": "screen",
+ "context": { "country": "nl", "city": "Amsterdam" },
+ "stickyFeatures": { "checkout": { "enabled": false } },
+ "expectedValue": {
+ "enabled": false,
+ "label": "Collect",
+ "policy": { "limit": 90, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "off"
+ },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "amsterdam",
+ "variableOverridePaths": [["netherlands"], ["netherlands", "amsterdam"]]
+ }
+ },
+ {
+ "name": "disabled-sources-with-no-override",
+ "variable": "screen",
+ "context": { "country": "us" },
+ "stickyFeatures": { "checkout": { "enabled": false } },
+ "expectedValue": {
+ "enabled": false,
+ "label": "Closed",
+ "policy": { "limit": 50, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "off"
+ },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "variable-prerequisite-gates-only-its-override",
+ "variable": "screen",
+ "context": { "country": "nl" },
+ "stickyVariables": { "gate": false },
+ "expectedValue": {
+ "enabled": true,
+ "label": "Buy",
+ "policy": { "limit": 90, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "control"
+ },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [["netherlands"]]
+ }
+ },
+ {
+ "name": "null-source-remains-null",
+ "variable": "screen",
+ "context": { "country": "nl" },
+ "stickyVariables": { "policy": null },
+ "expectedValue": {
+ "enabled": true,
+ "label": "Buy",
+ "policy": null,
+ "raw": { "decoded": true },
+ "variation": "control"
+ },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [["netherlands"], ["eligible"]]
+ }
+ },
+ {
+ "name": "whole-variable-sticky-bypasses-both-stages",
+ "variable": "screen",
+ "context": { "country": "nl" },
+ "stickyVariables": { "screen": { "pinned": true } },
+ "expectedValue": { "pinned": true },
+ "expectedEvaluation": { "reason": "sticky" }
+ },
+ {
+ "name": "nested-composition-consumes-final-overridden-source",
+ "variable": "nested",
+ "context": { "country": "nl", "city": "Amsterdam" },
+ "expectedValue": {
+ "screen": {
+ "enabled": true,
+ "label": "Collect",
+ "policy": { "limit": 100, "methods": ["card"] },
+ "raw": { "decoded": true },
+ "variation": "control"
+ }
+ },
+ "expectedEvaluation": { "reason": "variable_composed" }
+ },
+ {
+ "name": "literal-replacement-keeps-null-members",
+ "variable": "replacement",
+ "context": {},
+ "expectedValue": {
+ "enabled": false,
+ "label": null,
+ "policy": { "limit": 25, "methods": ["cash"] }
+ },
+ "expectedEvaluation": {
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [["literal"]]
+ }
+ },
+ {
+ "name": "composition-cycle-fails-whole-result",
+ "variable": "cycle",
+ "context": {},
+ "expectedValue": null,
+ "expectedEvaluation": { "reason": "error" }
+ },
+ {
+ "name": "bad-program-fails-whole-result",
+ "variable": "invalidProgram",
+ "context": {},
+ "expectedValue": null,
+ "expectedEvaluation": { "reason": "error" }
+ }
+ ]
+}
diff --git a/conformance/sdk-conditions.json b/conformance/sdk-conditions.json
new file mode 100644
index 0000000..339a41b
--- /dev/null
+++ b/conformance/sdk-conditions.json
@@ -0,0 +1,10990 @@
+{
+ "version": 1,
+ "capability": "unified-conditions",
+ "cases": [
+ {
+ "name": "wildcard",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "*"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "attribute leaf",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "segment leaf",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "dutch"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "feature leaf",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "global variable leaf",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "enabled"
+ ],
+ "operator": "equals",
+ "value": true
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "mixed group",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "dutch",
+ {
+ "feature": "gate"
+ },
+ {
+ "variable": "policy",
+ "path": [
+ "enabled"
+ ],
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "or": [
+ {
+ "feature": "missing"
+ },
+ {
+ "not": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "feature variable leaf",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "variablesSchema": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "enabled"
+ ],
+ "operator": "equals",
+ "value": true,
+ "feature": "gate"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "segment with segment reference",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "dependent": {
+ "conditions": "dutch"
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "dependent",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "segment with feature reference",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "dependent": {
+ "conditions": {
+ "feature": "gate"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "dependent",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "segment with variable reference",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "dependent": {
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "enabled"
+ ],
+ "operator": "equals",
+ "value": true
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "dependent",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "malformed segment cycle fails the evaluation",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "first": {
+ "conditions": "second"
+ },
+ "second": {
+ "conditions": {
+ "not": [
+ "first"
+ ]
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "first",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "reason": "error"
+ }
+ },
+ {
+ "name": "root gate reports its unmet conditions",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "us"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ {
+ "name": "force bypasses the root gate",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "force": [
+ {
+ "conditions": "*",
+ "enabled": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "us"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "forced"
+ }
+ },
+ {
+ "name": "sticky flag bypasses the root gate",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "us"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "sticky"
+ },
+ "options": {
+ "stickyFeatures": {
+ "subject": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ {
+ "name": "rule fall through after an unmet mixed condition",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "miss",
+ "conditions": [
+ "dutch",
+ {
+ "feature": "gate",
+ "enabled": false
+ }
+ ],
+ "percentage": 0
+ },
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "bare string always means segment",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "gate"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": "gate"
+ }
+ },
+ {
+ "name": "empty expression []",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": []
+ }
+ },
+ {
+ "name": "empty expression {\"and\":[]}",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ {
+ "name": "empty expression {\"or\":[]}",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "or": []
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "or": []
+ }
+ }
+ },
+ {
+ "name": "empty expression {\"not\":[]}",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "not": []
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "not": []
+ }
+ }
+ },
+ {
+ "name": "explicit null feature gate is not absent in schema 2",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": null
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": null
+ }
+ },
+ {
+ "name": "explicit null feature gate is not absent in schema 4",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": null
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": null
+ }
+ },
+ {
+ "name": "NOT with a null child still negates its ordinary false result",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "not": [
+ null
+ ]
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "legacy v3 fields retain their meaning",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "dutch",
+ "percentage": 100000
+ }
+ ],
+ "requiredFeatures": [
+ "gate"
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "legacy empty segment selector [] keeps v3 wildcard matching",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": [],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "legacy empty segment selector {\"and\":[]} keeps v3 wildcard matching",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": {
+ "and": []
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "schema 3 legacy input remains readable",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "3",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "dutch",
+ "percentage": 100000
+ }
+ ],
+ "requiredFeatures": [
+ "gate"
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "legacy root string feature is exposed as a normalized object leaf",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "requiredFeatures": [
+ "missing"
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": [
+ {
+ "feature": "missing"
+ }
+ ]
+ }
+ },
+ {
+ "name": "stringified mixed conditions retain all leaf kinds",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "[\"dutch\",{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"feature\":\"gate\"},{\"variable\":\"policy\",\"path\":[\"enabled\"],\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "mixed legacy and new root gates are ANDed",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "*",
+ "requiredFeatures": [
+ {
+ "feature": "gate",
+ "enabled": false
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "mixed legacy and new rule selectors are ANDed",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "segments": "missing"
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "unreleased requiredVariables is not read",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "requiredVariables": [
+ {
+ "variable": "missing",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "explicit null variable gate is not absent in schema 2",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": null
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet",
+ "conditions": null
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "explicit null variable gate is not absent in schema 4",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": null
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet",
+ "conditions": null
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "global root gate returns disabledValue",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": {
+ "feature": "gate",
+ "enabled": false
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "global root gate preserves useDefaultWhenDisabled",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": {
+ "feature": "gate",
+ "enabled": false
+ },
+ "useDefaultWhenDisabled": true
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "conditions_unmet"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "mixed legacy and new override selectors are ANDed",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "miss",
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "segments": "dutch",
+ "requiredFeatures": [
+ {
+ "feature": "gate",
+ "enabled": false
+ }
+ ],
+ "value": "incorrect"
+ },
+ {
+ "key": "all",
+ "conditions": "*",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "all"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "composed root gate returns null before sources",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "object",
+ "compose": "policy",
+ "conditions": {
+ "feature": "gate",
+ "enabled": false
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": null,
+ "reason": "conditions_unmet"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ }
+ ]
+ },
+ {
+ "name": "composed root gate uses disabledValue before sources",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "object",
+ "compose": "policy",
+ "conditions": {
+ "feature": "gate",
+ "enabled": false
+ },
+ "disabledValue": {
+ "enabled": false
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": {
+ "enabled": false
+ },
+ "reason": "conditions_unmet"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ }
+ ]
+ },
+ {
+ "name": "override ancestors AND with child nl",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "operations": [],
+ "onMatch": 1,
+ "onMiss": 2
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "conditions": {
+ "feature": "gate"
+ },
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "child"
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 2
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "country": "nl"
+ },
+ "expected": {
+ "value": "child",
+ "reason": "variable_override_rule"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "override ancestors AND with child us",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "operations": [],
+ "onMatch": 1,
+ "onMiss": 2
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "conditions": {
+ "feature": "gate"
+ },
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "child"
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 2
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "country": "us"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "module dependencies are ordered and tagged condition",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": [
+ "dutch",
+ {
+ "feature": "gate"
+ },
+ {
+ "variable": "policy",
+ "path": [
+ "enabled"
+ ],
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "feature": "gate"
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "variable": "policy",
+ "dependencyPurpose": "condition"
+ }
+ ]
+ },
+ {
+ "name": "short circuit avoids later dependency callbacks",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "feature": "gate"
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "us"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet"
+ },
+ "dependencies": []
+ },
+ {
+ "name": "malformed rule selector falls through",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "broken",
+ "conditions": "[\"dutch\"",
+ "percentage": 100000
+ },
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "malformed root gate reports normalized unmet conditions",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "{\"feature\":"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "or": []
+ }
+ }
+ },
+ {
+ "name": "NOT over a malformed segment cannot match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "broken": {
+ "conditions": "{\"attribute\":\"country\""
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": {
+ "not": [
+ "broken"
+ ]
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "malformed force selector is skipped",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": "[[",
+ "enabled": false
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "malformed global override selector is skipped",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "broken",
+ "conditions": "{\"not\":[",
+ "value": "incorrect"
+ },
+ {
+ "key": "all",
+ "conditions": "*",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "all"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 malformed segments string falls through",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "broken",
+ "segments": "[\"dutch\",",
+ "percentage": 100000
+ },
+ {
+ "key": "all",
+ "segments": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 empty [] at root",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": []
+ }
+ },
+ {
+ "name": "schema 2 empty [] at rule",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": [],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 2 empty [] at force",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": [],
+ "enabled": false
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "forced"
+ }
+ },
+ {
+ "name": "schema 2 empty [] at segment",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "empty": {
+ "conditions": []
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "empty",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 empty [] at variable root",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": []
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet",
+ "conditions": []
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 empty [] at override",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "empty",
+ "conditions": [],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "empty"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 empty [] at override program",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrideProgram": [
+ {
+ "key": "empty",
+ "keyPath": [
+ "empty"
+ ],
+ "conditions": [],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "incorrect"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at root",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at rule",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": {
+ "and": []
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at force",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": {
+ "and": []
+ },
+ "enabled": false
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "forced"
+ }
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at segment",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "empty": {
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "empty",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at variable root",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": {
+ "and": []
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet",
+ "conditions": {
+ "and": []
+ }
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at override",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "empty",
+ "conditions": {
+ "and": []
+ },
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "empty"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 empty {\"and\":[]} at override program",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrideProgram": [
+ {
+ "key": "empty",
+ "keyPath": [
+ "empty"
+ ],
+ "conditions": {
+ "and": []
+ },
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "incorrect"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 2 segment empty AND inside OR",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "nested": {
+ "conditions": {
+ "or": [
+ {
+ "and": []
+ },
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ]
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "nested",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 segment NOT of empty AND",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "nested": {
+ "conditions": {
+ "not": [
+ {
+ "and": []
+ }
+ ]
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "nested",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 2 segment empty OR",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "nested": {
+ "conditions": {
+ "or": []
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "nested",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 4 empty [] at root",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": []
+ }
+ },
+ {
+ "name": "schema 4 empty [] at rule",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": [],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 4 empty [] at force",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": [],
+ "enabled": false
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 empty [] at segment",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "empty": {
+ "conditions": []
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "empty",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 4 empty [] at variable root",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": []
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet",
+ "conditions": []
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 4 empty [] at override",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "empty",
+ "conditions": [],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 4 empty [] at override program",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrideProgram": [
+ {
+ "key": "empty",
+ "keyPath": [
+ "empty"
+ ],
+ "conditions": [],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "incorrect"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at root",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at rule",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": {
+ "and": []
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at force",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": {
+ "and": []
+ },
+ "enabled": false
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at segment",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "empty": {
+ "conditions": {
+ "and": []
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "empty",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at variable root",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": {
+ "and": []
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "disabled",
+ "reason": "conditions_unmet",
+ "conditions": {
+ "and": []
+ }
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at override",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "empty",
+ "conditions": {
+ "and": []
+ },
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 4 empty {\"and\":[]} at override program",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrideProgram": [
+ {
+ "key": "empty",
+ "keyPath": [
+ "empty"
+ ],
+ "conditions": {
+ "and": []
+ },
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "incorrect"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ },
+ "variable": "subject"
+ },
+ {
+ "name": "schema 4 segment empty AND inside OR",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "nested": {
+ "conditions": {
+ "or": [
+ {
+ "and": []
+ },
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ]
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "nested",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 4 segment NOT of empty AND",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "nested": {
+ "conditions": {
+ "not": [
+ {
+ "and": []
+ }
+ ]
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "nested",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 segment empty OR",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "nested": {
+ "conditions": {
+ "or": []
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "nested",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "no_match"
+ }
+ },
+ {
+ "name": "schema 2 parsed legacy segments [] match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": [],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 stringified legacy segments [] match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "[]",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 parsed legacy segments {\"and\":[]} match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": {
+ "and": []
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 stringified legacy segments {\"and\":[]} match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "{\"and\":[]}",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 3 parsed legacy segments [] match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "3",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": [],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 3 stringified legacy segments [] match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "3",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "[]",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 3 parsed legacy segments {\"and\":[]} match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "3",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": {
+ "and": []
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 3 stringified legacy segments {\"and\":[]} match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "3",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "{\"and\":[]}",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 parsed legacy segments [] match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": [],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 stringified legacy segments [] match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "[]",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 parsed legacy segments {\"and\":[]} match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": {
+ "and": []
+ },
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 stringified legacy segments {\"and\":[]} match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "{\"and\":[]}",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "numeric 1.0 equals integer at attribute",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "attribute": "count",
+ "operator": "equals",
+ "value": 1.0
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "count": {
+ "type": "integer",
+ "defaultValue": 1
+ }
+ }
+ },
+ "context": {
+ "count": 1
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "numeric 1.0 equals integer at variable",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "count",
+ "operator": "equals",
+ "value": 1.0
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "count": {
+ "type": "integer",
+ "defaultValue": 1
+ }
+ }
+ },
+ "context": {
+ "count": 1
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "numeric 1.0 equals integer at stringified attribute",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": "{\"attribute\":\"count\",\"operator\":\"equals\",\"value\":1.0}"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ },
+ "count": {
+ "type": "integer",
+ "defaultValue": 1
+ }
+ }
+ },
+ "context": {
+ "count": 1
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path numeric array index",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "items",
+ 1,
+ "count"
+ ],
+ "operator": "equals",
+ "value": 5
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path numeric 1.0 array index",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "items",
+ 1.0,
+ "count"
+ ],
+ "operator": "equals",
+ "value": 5
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path string array index",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "items",
+ "1",
+ "count"
+ ],
+ "operator": "equals",
+ "value": 5
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path array length",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "items",
+ "length"
+ ],
+ "operator": "equals",
+ "value": 2
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path literal dotted key",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "a.b",
+ "count"
+ ],
+ "operator": "equals",
+ "value": 1
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path missing path isNull",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "absent"
+ ],
+ "operator": "isNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "absent"
+ ],
+ "operator": "isNull"
+ }
+ }
+ },
+ {
+ "name": "compiled path missing path notNull",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "absent"
+ ],
+ "operator": "notNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "absent"
+ ],
+ "operator": "notNull"
+ }
+ }
+ },
+ {
+ "name": "compiled path explicit null isNull",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "nothing"
+ ],
+ "operator": "isNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled path explicit null notNull",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "nothing"
+ ],
+ "operator": "notNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "nothing"
+ ],
+ "operator": "notNull"
+ }
+ }
+ },
+ {
+ "name": "compiled path nonnull path notNull",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "policy",
+ "path": [
+ "items",
+ 1,
+ "count"
+ ],
+ "operator": "notNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "items": [
+ {
+ "count": 0
+ },
+ {
+ "count": 5
+ }
+ ],
+ "a.b": {
+ "count": 1
+ },
+ "a": {
+ "b": {
+ "count": 99
+ }
+ },
+ "nothing": null
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled missing path NOT isNull matches",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "not": [
+ {
+ "variable": "policy",
+ "path": [
+ "absent"
+ ],
+ "operator": "isNull"
+ }
+ ]
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "compiled missing path NOT notNull matches",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "not": [
+ {
+ "variable": "policy",
+ "path": [
+ "absent"
+ ],
+ "operator": "notNull"
+ }
+ ]
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "missing global variable getter is null before path traversal",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "missing",
+ "operator": "isNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "missing feature variable getter is null before path traversal",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "missing",
+ "operator": "isNull"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "expected": {
+ "enabled": true,
+ "reason": "rule"
+ }
+ },
+ {
+ "name": "schema 2 force mixes empty conditions with segments",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": [],
+ "segments": "dutch",
+ "enabled": false
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [],
+ "expected": {
+ "enabled": false,
+ "reason": "forced"
+ }
+ },
+ {
+ "name": "schema 4 force mixes empty conditions with segments",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "force": [
+ {
+ "conditions": [],
+ "segments": "dutch",
+ "enabled": false
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 requiredFeatures empty list is no gate",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "requiredFeatures": []
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 requiredFeatures nested AND OR NOT",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "requiredFeatures": [
+ {
+ "or": [
+ "missing",
+ {
+ "and": [
+ "gate",
+ {
+ "not": [
+ "off"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "missing",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "feature": "off",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 requiredFeatures empty OR is unmet",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "requiredFeatures": [
+ {
+ "or": []
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": [
+ {
+ "or": []
+ }
+ ]
+ }
+ },
+ {
+ "name": "schema 2 override requiredFeatures strings and segments",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "dep",
+ "segments": "dutch",
+ "requiredFeatures": [
+ "gate",
+ {
+ "feature": "off",
+ "enabled": false
+ }
+ ],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "feature": "off",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "dep",
+ "value": "matched"
+ }
+ },
+ {
+ "name": "string variable never equals a number",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "text",
+ "operator": "equals",
+ "value": 3
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "text": {
+ "type": "string",
+ "defaultValue": "3"
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "variable": "text",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "variable": "text",
+ "operator": "equals",
+ "value": 3
+ }
+ }
+ },
+ {
+ "name": "variable greaterThan with integer actual and floating expected",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "count",
+ "operator": "greaterThan",
+ "value": 2.5
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "count": {
+ "type": "integer",
+ "defaultValue": 3
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "variable": "count",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "variable greaterThan with double actual and integer expected",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "count",
+ "operator": "greaterThan",
+ "value": 3
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "count": {
+ "type": "double",
+ "defaultValue": 3.5
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "variable": "count",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "numeric getter integer equals expected 1.0 lexeme",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "integer",
+ "defaultValue": 1
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "variable": "subject",
+ "dependencies": [],
+ "expected": {
+ "reason": "variable_default",
+ "value": 1.0
+ }
+ },
+ {
+ "name": "absent feature satisfies enabled false",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "missing",
+ "enabled": false
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "missing",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "disabled feature matches its disabled variation",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "off",
+ "enabled": false,
+ "variation": "control"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": [],
+ "variations": [
+ {
+ "value": "control"
+ },
+ {
+ "value": "treatment"
+ }
+ ],
+ "disabledVariationValue": "control"
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "off",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "feature": "off",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "variation leaf without variations does not match",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variation": "control"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variation": "control"
+ }
+ }
+ },
+ {
+ "name": "sticky dependency feature drives the gate",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "off"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "options": {
+ "stickyFeatures": {
+ "off": {
+ "enabled": true
+ }
+ }
+ },
+ "dependencies": [
+ {
+ "feature": "off",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "cycle under NOT fails the evaluation",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "not": [
+ {
+ "feature": "loop"
+ }
+ ]
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ },
+ "loop": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "loop"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "loop",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "reason": "error"
+ }
+ },
+ {
+ "name": "short circuit OR never reaches a cycle",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "or": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "feature": "loop"
+ }
+ ]
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ },
+ "loop": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "loop"
+ }
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "global override referencing itself is a cycle",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "overrides": [
+ {
+ "key": "self",
+ "conditions": {
+ "variable": "subject",
+ "operator": "equals",
+ "value": "default"
+ },
+ "value": "incorrect"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "variable": "subject",
+ "dependencies": [],
+ "expected": {
+ "reason": "error",
+ "value": null
+ }
+ },
+ {
+ "name": "segment and direct feature leaf memoize one dependency",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ },
+ "needsGate": {
+ "conditions": {
+ "feature": "gate"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": [
+ "needsGate",
+ {
+ "feature": "gate"
+ },
+ "needsGate"
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "variable leaf over an alias reports condition then composition",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "variable": "alias",
+ "path": [
+ "limit"
+ ],
+ "operator": "equals",
+ "value": 3
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "alias": {
+ "type": "object",
+ "compose": "policy"
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "variable": "alias",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "variable": "policy",
+ "dependencyPurpose": "composition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "composition override never mutates its source in one evaluation",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": [
+ {
+ "variable": "alias",
+ "path": [
+ "limit"
+ ],
+ "operator": "equals",
+ "value": 90
+ },
+ {
+ "variable": "policy",
+ "path": [
+ "limit"
+ ],
+ "operator": "equals",
+ "value": 3
+ },
+ {
+ "variable": "alias",
+ "path": [
+ "limit"
+ ],
+ "operator": "equals",
+ "value": 90
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "alias": {
+ "type": "object",
+ "compose": "policy",
+ "overrideProgram": [
+ {
+ "key": "all",
+ "keyPath": [
+ "all"
+ ],
+ "conditions": "*",
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "limit"
+ ],
+ "value": 90
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "variable": "alias",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "variable": "policy",
+ "dependencyPurpose": "composition"
+ },
+ {
+ "variable": "policy",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "malformed composition gate returns disabledValue and empty OR metadata",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "object",
+ "compose": "policy",
+ "conditions": "{\"or\":[",
+ "disabledValue": {
+ "blocked": true
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "variable": "subject",
+ "dependencies": [],
+ "expected": {
+ "reason": "conditions_unmet",
+ "conditions": {
+ "or": []
+ },
+ "value": {
+ "blocked": true
+ }
+ }
+ },
+ {
+ "name": "alias chain gates at each hop in nl",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "object",
+ "compose": "hop2"
+ },
+ "hop1": {
+ "type": "object",
+ "compose": "policy",
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "disabledValue": {
+ "hop": 1
+ }
+ },
+ "hop2": {
+ "type": "object",
+ "compose": "hop1",
+ "conditions": {
+ "not": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "nl"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "variable": "hop2",
+ "dependencyPurpose": "composition"
+ },
+ {
+ "variable": "hop1",
+ "dependencyPurpose": "composition"
+ },
+ {
+ "variable": "policy",
+ "dependencyPurpose": "composition"
+ }
+ ],
+ "expected": {
+ "reason": "variable_composed",
+ "value": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ },
+ {
+ "name": "alias chain gates at each hop in de",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "object",
+ "compose": "hop2"
+ },
+ "hop1": {
+ "type": "object",
+ "compose": "policy",
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "disabledValue": {
+ "hop": 1
+ }
+ },
+ "hop2": {
+ "type": "object",
+ "compose": "hop1",
+ "conditions": {
+ "not": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "de"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "variable": "hop2",
+ "dependencyPurpose": "composition"
+ },
+ {
+ "variable": "hop1",
+ "dependencyPurpose": "composition"
+ }
+ ],
+ "expected": {
+ "reason": "variable_composed",
+ "value": {
+ "hop": 1
+ }
+ }
+ },
+ {
+ "name": "alias chain gates at each hop in us",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "object",
+ "compose": "hop2"
+ },
+ "hop1": {
+ "type": "object",
+ "compose": "policy",
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ "disabledValue": {
+ "hop": 1
+ }
+ },
+ "hop2": {
+ "type": "object",
+ "compose": "hop1",
+ "conditions": {
+ "not": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "context": {
+ "country": "us"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "variable": "hop2",
+ "dependencyPurpose": "composition"
+ }
+ ],
+ "expected": {
+ "reason": "variable_composed",
+ "value": null
+ }
+ },
+ {
+ "name": "root gate reads a composed alias",
+ "type": "variable",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "disabledValue": "disabled",
+ "conditions": {
+ "variable": "alias",
+ "operator": "equals",
+ "value": true
+ }
+ },
+ "source": {
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "alias": {
+ "type": "boolean",
+ "compose": "source"
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "variable": "subject",
+ "dependencies": [
+ {
+ "variable": "alias",
+ "dependencyPurpose": "condition"
+ },
+ {
+ "variable": "source",
+ "dependencyPurpose": "composition"
+ }
+ ],
+ "expected": {
+ "reason": "variable_default",
+ "value": "default"
+ }
+ },
+ {
+ "name": "schema 2 parsed empty [] at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": [],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 parsed empty [] at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": [],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 stringified empty [] at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "[]",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 stringified empty [] at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "[]",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 parsed empty {\"and\":[]} at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": {
+ "and": []
+ },
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 parsed empty {\"and\":[]} at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": {
+ "and": []
+ },
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 stringified empty {\"and\":[]} at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "{\"and\":[]}",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 2 stringified empty {\"and\":[]} at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "{\"and\":[]}",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": true,
+ "reason": "rule",
+ "ruleKey": "all"
+ }
+ },
+ {
+ "name": "schema 4 parsed empty [] at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": [],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 parsed empty [] at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": [],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 stringified empty [] at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "[]",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 stringified empty [] at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "[]",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 parsed empty {\"and\":[]} at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": {
+ "and": []
+ },
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 parsed empty {\"and\":[]} at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": {
+ "and": []
+ },
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 stringified empty {\"and\":[]} at feature variable rule override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "{\"and\":[]}",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ }
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ },
+ {
+ "name": "schema 4 stringified empty {\"and\":[]} at feature variable variation override",
+ "type": "flag",
+ "feature": "subject",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "conditions-conformance",
+ "segments": {
+ "dutch": {
+ "conditions": {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ }
+ },
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000,
+ "variation": "control"
+ }
+ ],
+ "variablesSchema": {
+ "label": {
+ "type": "string",
+ "defaultValue": "default"
+ }
+ },
+ "variations": [
+ {
+ "value": "control",
+ "variableOverrides": {
+ "label": [
+ {
+ "conditions": "{\"and\":[]}",
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "subject": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ],
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ },
+ "off": {
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": {
+ "enabled": true,
+ "limit": 3
+ }
+ }
+ }
+ },
+ "context": {
+ "userId": "conditions-user",
+ "country": "nl"
+ },
+ "dependencies": [
+ {
+ "feature": "gate",
+ "variable": "label",
+ "dependencyPurpose": "condition"
+ }
+ ],
+ "expected": {
+ "enabled": false,
+ "reason": "conditions_unmet",
+ "conditions": {
+ "feature": "gate",
+ "variable": "label",
+ "operator": "equals",
+ "value": "matched"
+ }
+ }
+ }
+ ]
+}
diff --git a/conformance/sdk-diagnostics.json b/conformance/sdk-diagnostics.json
new file mode 100644
index 0000000..ec57d42
--- /dev/null
+++ b/conformance/sdk-diagnostics.json
@@ -0,0 +1,196 @@
+{
+ "kind": "featurevisor.sdk-diagnostics",
+ "version": 1,
+ "cases": [
+ {
+ "name": "eager and lazy diagnostics cache before reentry and reset only replaced entities",
+ "operations": [
+ {
+ "action": "setDatafile",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "r2",
+ "segments": {
+ "broken": {
+ "conditions": "[{\"attribute\":"
+ }
+ },
+ "features": {
+ "a": {
+ "bucketBy": "userId",
+ "force": [
+ {
+ "conditions": "{\"and\":[",
+ "enabled": false
+ }
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ }
+ },
+ "replace": true,
+ "expectedResult": true,
+ "diagnostics": [
+ {
+ "code": "conditions_parse_error",
+ "level": "error",
+ "details": {
+ "conditions": "[{\"attribute\":",
+ "entityType": "segment",
+ "entityKey": "broken"
+ },
+ "revision": "r2"
+ },
+ {
+ "code": "conditions_parse_error",
+ "level": "error",
+ "details": {
+ "conditions": "{\"and\":[",
+ "entityType": "feature",
+ "entityKey": "a"
+ },
+ "revision": "r2"
+ }
+ ]
+ },
+ {
+ "action": "isEnabled",
+ "feature": "a",
+ "expectedResult": true,
+ "diagnostics": []
+ },
+ {
+ "action": "setDatafile",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "r4",
+ "segments": {},
+ "features": {
+ "b": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "broken",
+ "conditions": "[\"x\"",
+ "percentage": 100000
+ },
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ }
+ },
+ "replace": true,
+ "expectedResult": true,
+ "diagnostics": []
+ },
+ {
+ "action": "isEnabled",
+ "feature": "b",
+ "expectedResult": true,
+ "diagnostics": [
+ {
+ "code": "conditions_parse_error",
+ "level": "error",
+ "details": {
+ "conditions": "[\"x\"",
+ "entityType": "feature",
+ "entityKey": "b"
+ },
+ "revision": "r4"
+ }
+ ]
+ },
+ {
+ "action": "isEnabled",
+ "feature": "b",
+ "expectedResult": true,
+ "diagnostics": []
+ },
+ {
+ "action": "setDatafile",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "r5",
+ "segments": {},
+ "features": {
+ "c": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ }
+ },
+ "replace": false,
+ "expectedResult": true,
+ "diagnostics": []
+ },
+ {
+ "action": "isEnabled",
+ "feature": "b",
+ "expectedResult": true,
+ "diagnostics": []
+ },
+ {
+ "action": "setDatafile",
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "r6",
+ "segments": {},
+ "features": {
+ "b": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "broken",
+ "conditions": "[\"x\"",
+ "percentage": 100000
+ },
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ }
+ },
+ "replace": false,
+ "expectedResult": true,
+ "diagnostics": []
+ },
+ {
+ "action": "isEnabled",
+ "feature": "b",
+ "expectedResult": true,
+ "diagnostics": [
+ {
+ "code": "conditions_parse_error",
+ "level": "error",
+ "details": {
+ "conditions": "[\"x\"",
+ "entityType": "feature",
+ "entityKey": "b"
+ },
+ "revision": "r6"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/conformance/sdk-stateful.json b/conformance/sdk-stateful.json
new file mode 100644
index 0000000..fdacf88
--- /dev/null
+++ b/conformance/sdk-stateful.json
@@ -0,0 +1,4023 @@
+{
+ "kind": "featurevisor.sdk-stateful",
+ "schemaVersion": 1,
+ "cases": [
+ {
+ "name": "partial and full variable updates propagate through same hash dependents",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "initial",
+ "segments": {},
+ "features": {
+ "dependent": {
+ "hash": "dependent",
+ "bucketBy": "userId",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "transitive": {
+ "hash": "transitive",
+ "bucketBy": "userId",
+ "requiredFeatures": ["dependent"],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "gate": { "hash": "gate-1", "type": "boolean", "defaultValue": false },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }]
+ }
+ }
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "partial",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": { "hash": "gate-2", "type": "boolean", "defaultValue": true }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "partial",
+ "previousRevision": "initial",
+ "revisionChanged": true,
+ "features": ["dependent", "transitive"],
+ "variables": ["gate", "message"],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ { "target": "root", "method": "isEnabled", "args": ["dependent"], "value": true },
+ { "target": "root", "method": "isEnabled", "args": ["transitive"], "value": true },
+ { "target": "root", "method": "getVariable", "args": ["message"], "value": "ready" }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "full",
+ "segments": {},
+ "features": {
+ "dependent": {
+ "hash": "dependent",
+ "bucketBy": "userId",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "transitive": {
+ "hash": "transitive",
+ "bucketBy": "userId",
+ "requiredFeatures": ["dependent"],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "gate": { "hash": "gate-3", "type": "boolean", "defaultValue": false },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }]
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "full",
+ "previousRevision": "partial",
+ "revisionChanged": true,
+ "features": ["dependent", "transitive"],
+ "variables": ["gate", "message"],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ { "target": "root", "method": "isEnabled", "args": ["dependent"], "value": false },
+ { "target": "root", "method": "isEnabled", "args": ["transitive"], "value": false },
+ { "target": "root", "method": "getVariable", "args": ["message"], "value": "blocked" }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "partial and full updates use new and old dependency graphs",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "initial",
+ "segments": {},
+ "features": {
+ "dependent": {
+ "hash": "dependent",
+ "bucketBy": "userId",
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "transitive": {
+ "hash": "transitive",
+ "bucketBy": "userId",
+ "requiredFeatures": ["dependent"],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "gate": { "hash": "gate-1", "type": "boolean", "defaultValue": false },
+ "message": { "hash": "message", "type": "string", "defaultValue": "ready" }
+ }
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "added",
+ "segments": {},
+ "features": {
+ "dependent": {
+ "hash": "dependent",
+ "bucketBy": "userId",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "gate": { "hash": "gate-2", "type": "boolean", "defaultValue": false },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }]
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "added",
+ "previousRevision": "initial",
+ "revisionChanged": true,
+ "features": ["dependent", "transitive"],
+ "variables": ["gate", "message"],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ { "target": "root", "method": "isEnabled", "args": ["dependent"], "value": false },
+ { "target": "root", "method": "getVariable", "args": ["message"], "value": "blocked" }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "removed",
+ "segments": {},
+ "features": {
+ "dependent": {
+ "hash": "dependent",
+ "bucketBy": "userId",
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "transitive": {
+ "hash": "transitive",
+ "bucketBy": "userId",
+ "requiredFeatures": ["dependent"],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "gate": { "hash": "gate-3", "type": "boolean", "defaultValue": false },
+ "message": { "hash": "message", "type": "string", "defaultValue": "ready" }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "removed",
+ "previousRevision": "added",
+ "revisionChanged": true,
+ "features": ["dependent", "transitive"],
+ "variables": ["gate", "message"],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ { "target": "root", "method": "isEnabled", "args": ["dependent"], "value": true },
+ { "target": "root", "method": "getVariable", "args": ["message"], "value": "ready" }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "rejected JSON preserves previous state",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "stable",
+ "segments": {},
+ "features": {
+ "stable": {
+ "hash": "stable",
+ "bucketBy": "userId",
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ }
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": "{not JSON",
+ "expect": {
+ "error": { "code": "invalid_datafile" },
+ "events": [],
+ "results": [
+ { "target": "root", "method": "getRevision", "args": [], "value": "stable" },
+ { "target": "root", "method": "isEnabled", "args": ["stable"], "value": true }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "root and child sticky payloads stay isolated",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "sticky",
+ "segments": {},
+ "features": {
+ "flag": {
+ "hash": "flag",
+ "bucketBy": "userId",
+ "variablesSchema": {
+ "message": { "type": "string", "defaultValue": "default-feature" }
+ },
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ }
+ },
+ "variables": {
+ "message": { "hash": "message", "type": "string", "defaultValue": "default-global" }
+ }
+ }
+ },
+ "operations": [
+ {
+ "action": "spawn",
+ "parent": "root",
+ "target": "child",
+ "context": { "tenant": "child" },
+ "options": {
+ "stickyFeatures": { "flag": { "enabled": false } },
+ "stickyVariables": { "message": "child-global" }
+ },
+ "expect": {
+ "events": [],
+ "results": [
+ { "target": "root", "method": "isEnabled", "args": ["flag"], "value": true },
+ { "target": "child", "method": "isEnabled", "args": ["flag"], "value": false },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": ["message"],
+ "value": "default-global"
+ },
+ {
+ "target": "child",
+ "method": "getVariable",
+ "args": ["message"],
+ "value": "child-global"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setStickyFeatures",
+ "target": "root",
+ "replace": true,
+ "stickyFeatures": {
+ "flag": { "enabled": true, "variables": { "message": "root-feature" } }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "sticky_features_set",
+ "details": { "features": ["flag"], "replaced": true }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": ["flag", "message"],
+ "value": "root-feature"
+ },
+ { "target": "child", "method": "isEnabled", "args": ["flag"], "value": false }
+ ]
+ }
+ },
+ {
+ "action": "setStickyFeatures",
+ "target": "child",
+ "replace": true,
+ "stickyFeatures": {
+ "flag": { "enabled": true, "variables": { "message": "child-feature" } }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "child",
+ "name": "sticky_features_set",
+ "details": { "features": ["flag"], "replaced": true }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": ["flag", "message"],
+ "value": "root-feature"
+ },
+ {
+ "target": "child",
+ "method": "getVariable",
+ "args": ["flag", "message"],
+ "value": "child-feature"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": ["message"],
+ "value": "default-global"
+ },
+ {
+ "target": "child",
+ "method": "getVariable",
+ "args": ["message"],
+ "value": "child-global"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setStickyVariables",
+ "target": "child",
+ "replace": true,
+ "stickyVariables": { "message": "child-global-updated" },
+ "expect": {
+ "events": [
+ {
+ "target": "child",
+ "name": "sticky_variables_set",
+ "details": { "variables": ["message"], "replaced": true }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": ["message"],
+ "value": "default-global"
+ },
+ {
+ "target": "child",
+ "method": "getVariable",
+ "args": ["message"],
+ "value": "child-global-updated"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "cached graph refreshes after segment only partial updates",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "0",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g1",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "context": {
+ "userId": "subject",
+ "country": "nl"
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "1",
+ "segments": {},
+ "features": {
+ "g": {
+ "hash": "g2",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "1",
+ "previousRevision": "0",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "g"
+ ],
+ "value": true
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "2",
+ "segments": {
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "2",
+ "previousRevision": "1",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "g"
+ ],
+ "value": true
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "3",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "3",
+ "previousRevision": "2",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "g"
+ ],
+ "value": true
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "cache free graph history seed 5",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "0",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g0",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate0",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "context": {
+ "userId": "subject",
+ "country": "nl"
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "1",
+ "segments": {},
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "1",
+ "previousRevision": "0",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "2",
+ "segments": {},
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "2",
+ "previousRevision": "1",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "3",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "type": "boolean",
+ "defaultValue": false
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "3",
+ "previousRevision": "2",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "4",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g4",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "type": "boolean",
+ "defaultValue": false
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "4",
+ "previousRevision": "3",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [
+ "gate"
+ ],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "5",
+ "segments": {
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "5",
+ "previousRevision": "4",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "6",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "type": "boolean",
+ "defaultValue": true
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "6",
+ "previousRevision": "5",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "7",
+ "segments": {
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "7",
+ "previousRevision": "6",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [
+ "alias",
+ "gate",
+ "message"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "8",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate8",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "8",
+ "previousRevision": "7",
+ "revisionChanged": true,
+ "features": [
+ "f3",
+ "g"
+ ],
+ "variables": [
+ "alias",
+ "gate",
+ "message"
+ ],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "9",
+ "segments": {},
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "9",
+ "previousRevision": "8",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "10",
+ "segments": {
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "10",
+ "previousRevision": "9",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "11",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "hash": "gate11",
+ "type": "boolean",
+ "defaultValue": false
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "11",
+ "previousRevision": "10",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [
+ "alias",
+ "gate",
+ "message"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "blocked"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "blocked"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "12",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate11",
+ "type": "boolean",
+ "defaultValue": false
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "12",
+ "previousRevision": "11",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "blocked"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "blocked"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "cache free graph history seed 11",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "0",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g0",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate0",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "context": {
+ "userId": "subject",
+ "country": "nl"
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "1",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "1",
+ "previousRevision": "0",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [
+ "alias",
+ "message"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "blocked"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "blocked"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "2",
+ "segments": {
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "2",
+ "previousRevision": "1",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [
+ "alias",
+ "message"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "3",
+ "segments": {},
+ "features": {
+ "g": {
+ "hash": "g3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "3",
+ "previousRevision": "2",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "4",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g4",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate0",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "4",
+ "previousRevision": "3",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "5",
+ "segments": {},
+ "features": {
+ "g": {
+ "hash": "g5",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "5",
+ "previousRevision": "4",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "6",
+ "segments": {},
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "6",
+ "previousRevision": "5",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "7",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "7",
+ "previousRevision": "6",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "8",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate8",
+ "type": "boolean",
+ "defaultValue": false
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "8",
+ "previousRevision": "7",
+ "revisionChanged": true,
+ "features": [
+ "f3",
+ "g"
+ ],
+ "variables": [
+ "alias",
+ "gate",
+ "message"
+ ],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "blocked"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "blocked"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "9",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "9",
+ "previousRevision": "8",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "blocked"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "blocked"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "10",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "hash": "gate10",
+ "type": "boolean",
+ "defaultValue": true
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "10",
+ "previousRevision": "9",
+ "revisionChanged": true,
+ "features": [
+ "f3"
+ ],
+ "variables": [
+ "alias",
+ "gate",
+ "message"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "11",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "11",
+ "previousRevision": "10",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "12",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate10",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "12",
+ "previousRevision": "11",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "cache free graph history seed 29",
+ "initial": {
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "0",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": [
+ "s4"
+ ],
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g0",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate0",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "context": {
+ "userId": "subject",
+ "country": "nl"
+ }
+ },
+ "operations": [
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "1",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "hash": "gate1",
+ "type": "boolean",
+ "defaultValue": false
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "1",
+ "previousRevision": "0",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "2",
+ "segments": {},
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "2",
+ "previousRevision": "1",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": false
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "3",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "type": "boolean",
+ "defaultValue": true
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "3",
+ "previousRevision": "2",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "4",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ "s0"
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ },
+ "g": {
+ "hash": "g0",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate4",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "4",
+ "previousRevision": "3",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "5",
+ "segments": {},
+ "features": {
+ "g": {
+ "hash": "g5",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "5",
+ "previousRevision": "4",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "6",
+ "segments": {},
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "6",
+ "previousRevision": "5",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "7",
+ "segments": {
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "7",
+ "previousRevision": "6",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "8",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "hash": "gate4",
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "8",
+ "previousRevision": "7",
+ "revisionChanged": true,
+ "features": [
+ "g"
+ ],
+ "variables": [],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "9",
+ "segments": {},
+ "features": {},
+ "variables": {
+ "gate": {
+ "type": "boolean",
+ "defaultValue": true
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "9",
+ "previousRevision": "8",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "10",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "10",
+ "previousRevision": "9",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": false,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "11",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ }
+ },
+ "features": {},
+ "variables": {}
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "11",
+ "previousRevision": "10",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": false
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ },
+ {
+ "action": "setDatafile",
+ "target": "root",
+ "replace": true,
+ "datafile": {
+ "schemaVersion": "4",
+ "revision": "12",
+ "segments": {
+ "s0": {
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "de"
+ }
+ ]
+ },
+ "s4": {
+ "conditions": [
+ {
+ "variable": "gate",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "features": {
+ "f3": {
+ "hash": "f3",
+ "bucketBy": "userId",
+ "conditions": "*",
+ "traffic": [
+ {
+ "key": "all",
+ "conditions": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "gate": {
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "message": {
+ "hash": "message",
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [
+ {
+ "feature": "f3"
+ }
+ ]
+ },
+ "alias": {
+ "hash": "alias",
+ "type": "string",
+ "compose": "message"
+ }
+ }
+ },
+ "expect": {
+ "events": [
+ {
+ "target": "root",
+ "name": "datafile_set",
+ "details": {
+ "revision": "12",
+ "previousRevision": "11",
+ "revisionChanged": true,
+ "features": [],
+ "variables": [
+ "gate"
+ ],
+ "replaced": true
+ }
+ }
+ ],
+ "results": [
+ {
+ "target": "root",
+ "method": "isEnabled",
+ "args": [
+ "f3"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "gate"
+ ],
+ "value": true
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "message"
+ ],
+ "value": "ready"
+ },
+ {
+ "target": "root",
+ "method": "getVariable",
+ "args": [
+ "alias"
+ ],
+ "value": "ready"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/conformance/sdk-v3.json b/conformance/sdk-v3.json
index d5870a5..61bc432 100644
--- a/conformance/sdk-v3.json
+++ b/conformance/sdk-v3.json
@@ -1,6 +1,41 @@
{
- "version": 6,
- "description": "Featurevisor v3 cross SDK compatibility contracts",
+ "version": 15,
+ "description": "Featurevisor SDK compatibility contracts, including evaluated variable requirements",
+ "requiredFeatureGroupInputs": [
+ { "a": false, "b": false },
+ { "a": false, "b": true },
+ { "a": true, "b": false },
+ { "a": true, "b": true }
+ ],
+ "requiredFeatureGroups": [
+ { "name": "implicit AND", "requirements": ["a", "b"], "results": [false, false, false, true] },
+ {
+ "name": "explicit AND",
+ "requirements": [{ "and": ["a", "b"] }],
+ "results": [false, false, false, true]
+ },
+ { "name": "OR", "requirements": [{ "or": ["a", "b"] }], "results": [false, true, true, true] },
+ {
+ "name": "NOT means not all",
+ "requirements": [{ "not": ["a", "b"] }],
+ "results": [true, true, true, false]
+ },
+ {
+ "name": "NOT OR means neither",
+ "requirements": [{ "not": [{ "or": ["a", "b"] }] }],
+ "results": [true, false, false, false]
+ },
+ {
+ "name": "disabled leaf in OR",
+ "requirements": [{ "or": [{ "feature": "a", "enabled": false }, "b"] }],
+ "results": [true, true, false, true]
+ },
+ {
+ "name": "empty NOT defensive fallback",
+ "requirements": [{ "not": [] }],
+ "results": [false, false, false, false]
+ }
+ ],
"bucketing": {
"minimum": 0,
"maximum": 100000,
@@ -237,25 +272,25 @@
"name": "required unmet with disabled value",
"key": "requiredDisabled",
"expectedValue": "disabled",
- "expectedReason": "required_features_unmet"
+ "expectedReason": "conditions_unmet"
},
{
"name": "required unmet without value",
"key": "requiredMissingValue",
- "expectedReason": "required_features_unmet"
+ "expectedReason": "conditions_unmet"
},
{
"name": "required unmet with caller default",
"key": "requiredMissingValue",
"defaultVariableValue": "caller",
"expectedValue": "caller",
- "expectedReason": "required_features_unmet"
+ "expectedReason": "conditions_unmet"
},
{
"name": "required unmet using variable default",
"key": "requiredUsesDefault",
"expectedValue": "default",
- "expectedReason": "required_features_unmet"
+ "expectedReason": "conditions_unmet"
},
{
"name": "required variation matched",
@@ -583,11 +618,30 @@
"unmetMultipleRequirements": {
"bucketBy": "userId",
"requiredFeatures": ["enabledFeature", { "feature": "disabledFeature", "enabled": true }],
+ "variablesSchema": { "message": { "type": "string", "defaultValue": "ready" } },
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "unmetWithDisabledValues": {
+ "bucketBy": "userId",
+ "requiredFeatures": ["disabledFeature"],
+ "disabledVariationValue": "off",
+ "variablesSchema": {
+ "message": { "type": "string", "defaultValue": "ready", "disabledValue": "blocked" },
+ "fallback": {
+ "type": "string",
+ "defaultValue": "ready",
+ "useDefaultWhenDisabled": true
+ }
+ },
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "unmetVariableRequirement": {
+ "bucketBy": "userId",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }],
"traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
},
- "canonicalPrecedence": {
+ "canonicalRequirement": {
"bucketBy": "userId",
- "required": ["disabledFeature"],
"requiredFeatures": ["enabledFeature"],
"traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
},
@@ -618,6 +672,21 @@
}
]
}
+ },
+ "variables": {
+ "gate": { "type": "boolean", "defaultValue": false },
+ "featureGatedMessage": {
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "requiredFeatures": ["disabledFeature"]
+ },
+ "variableGatedMessage": {
+ "type": "string",
+ "defaultValue": "ready",
+ "disabledValue": "blocked",
+ "conditions": [{ "variable": "gate", "operator": "equals", "value": true }]
+ }
}
},
"cases": [
@@ -657,11 +726,70 @@
"expectedEnabled": false
},
{
- "name": "requiredFeatures takes precedence over required",
- "feature": "canonicalPrecedence",
+ "name": "canonical requiredFeatures",
+ "feature": "canonicalRequirement",
"expectedEnabled": true
}
],
+ "evaluationCases": [
+ {
+ "name": "feature flag uses the canonical unmet feature reason",
+ "type": "flag",
+ "feature": "unmetMultipleRequirements",
+ "expected": { "reason": "conditions_unmet", "enabled": false }
+ },
+ {
+ "name": "variation retains its disabled reason",
+ "type": "variation",
+ "feature": "unmetMultipleRequirements",
+ "expected": { "reason": "disabled" }
+ },
+ {
+ "name": "feature variable retains its disabled reason",
+ "type": "variable",
+ "feature": "unmetMultipleRequirements",
+ "variable": "message",
+ "expected": { "reason": "disabled" }
+ },
+ {
+ "name": "variation retains its disabled value policy",
+ "type": "variation",
+ "feature": "unmetWithDisabledValues",
+ "expected": { "reason": "variation_disabled", "variationValue": "off", "enabled": false }
+ },
+ {
+ "name": "feature variable retains its explicit disabled value",
+ "type": "variable",
+ "feature": "unmetWithDisabledValues",
+ "variable": "message",
+ "expected": { "reason": "variable_disabled", "variableValue": "blocked", "enabled": false }
+ },
+ {
+ "name": "feature variable retains its disabled default policy",
+ "type": "variable",
+ "feature": "unmetWithDisabledValues",
+ "variable": "fallback",
+ "expected": { "reason": "variable_default", "variableValue": "ready", "enabled": false }
+ },
+ {
+ "name": "global variable uses the canonical unmet feature reason",
+ "type": "variable",
+ "variable": "featureGatedMessage",
+ "expected": { "reason": "conditions_unmet", "variableValue": "blocked" }
+ },
+ {
+ "name": "feature flag keeps the distinct unmet variable reason",
+ "type": "flag",
+ "feature": "unmetVariableRequirement",
+ "expected": { "reason": "conditions_unmet", "enabled": false }
+ },
+ {
+ "name": "global variable keeps the distinct unmet variable reason",
+ "type": "variable",
+ "variable": "variableGatedMessage",
+ "expected": { "reason": "conditions_unmet", "variableValue": "blocked" }
+ }
+ ],
"featureVariableCase": {
"feature": "featureVariableOverride",
"variable": "message",
@@ -688,6 +816,7 @@
"portableConditions": {
"regexFlags": ["g", "i", "m", "s"],
"rejectedRegexFlags": ["d", "u", "v", "y"],
+ "regexEngineLimits": "Portable shorthand cases cover ASCII digit and word classes, their negations on BMP characters, escaped literals, and case insensitive word shorthand outside character classes. Astral and other UTF-16 code unit distinctions, case insensitive word shorthand inside character classes, and broader regular expression engine equivalence are not guaranteed. Whitespace shorthand retains each native engine's current behaviour.",
"dateFormat": "ISO 8601 with an explicit timezone",
"dates": [
"2024-01-01T00:00:00Z",
@@ -701,96 +830,1844 @@
},
"conditionCases": [
{
- "name": "strict primitive equality",
+ "name": "notEquals: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "notEquals", "value": 1.0 },
+ "context": { "values": 1 },
+ "expected": false
+ },
+ {
+ "name": "notEquals: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "notEquals", "value": 1 },
+ "context": { "values": 1.0 },
+ "expected": false
+ },
+ {
+ "name": "in: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "in", "value": [1.0] },
+ "context": { "values": 1 },
+ "expected": true
+ },
+ {
+ "name": "in: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "in", "value": [1] },
+ "context": { "values": 1.0 },
+ "expected": true
+ },
+ {
+ "name": "notIn: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "notIn", "value": [1.0] },
+ "context": { "values": 1 },
+ "expected": false
+ },
+ {
+ "name": "notIn: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "notIn", "value": [1] },
+ "context": { "values": 1.0 },
+ "expected": false
+ },
+ {
+ "name": "greaterThan: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "greaterThan", "value": 1.0 },
+ "context": { "values": 1 },
+ "expected": false
+ },
+ {
+ "name": "greaterThan: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "greaterThan", "value": 1 },
+ "context": { "values": 1.0 },
+ "expected": false
+ },
+ {
+ "name": "greaterThanOrEquals: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "greaterThanOrEquals", "value": 1.0 },
+ "context": { "values": 1 },
+ "expected": true
+ },
+ {
+ "name": "greaterThanOrEquals: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "greaterThanOrEquals", "value": 1 },
+ "context": { "values": 1.0 },
+ "expected": true
+ },
+ {
+ "name": "lessThan: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "lessThan", "value": 1.0 },
+ "context": { "values": 1 },
+ "expected": false
+ },
+ {
+ "name": "lessThan: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "lessThan", "value": 1 },
+ "context": { "values": 1.0 },
+ "expected": false
+ },
+ {
+ "name": "lessThanOrEquals: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "lessThanOrEquals", "value": 1.0 },
+ "context": { "values": 1 },
+ "expected": true
+ },
+ {
+ "name": "lessThanOrEquals: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "lessThanOrEquals", "value": 1 },
+ "context": { "values": 1.0 },
+ "expected": true
+ },
+ {
+ "name": "equals: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "equals", "value": 1.0 },
+ "context": { "values": 1 },
+ "expected": true
+ },
+ {
+ "name": "equals: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "equals", "value": 1 },
+ "context": { "values": 1.0 },
+ "expected": true
+ },
+ {
+ "name": "includes: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "includes", "value": 1.0 },
+ "context": { "values": [1] },
+ "expected": true
+ },
+ {
+ "name": "includes: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "includes", "value": 1 },
+ "context": { "values": [1.0] },
+ "expected": true
+ },
+ {
+ "name": "includesAny: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "includesAny", "value": [1.0] },
+ "context": { "values": [1] },
+ "expected": true
+ },
+ {
+ "name": "includesAny: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "includesAny", "value": [1] },
+ "context": { "values": [1.0] },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: integer actual and floating candidate",
+ "condition": { "attribute": "values", "operator": "notIncludesAny", "value": [1.0] },
+ "context": { "values": [1] },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: floating actual and integer candidate",
+ "condition": { "attribute": "values", "operator": "notIncludesAny", "value": [1] },
+ "context": { "values": [1.0] },
+ "expected": false
+ },
+ {
+ "name": "equals: large finite numbers",
+ "condition": { "attribute": "values", "operator": "equals", "value": 1e30 },
+ "context": { "values": 1e30 },
+ "expected": true
+ },
+ {
+ "name": "includes: large finite numbers",
+ "condition": { "attribute": "values", "operator": "includes", "value": 1e30 },
+ "context": { "values": [1e30] },
+ "expected": true
+ },
+ {
+ "name": "includesAny: large finite numbers",
+ "condition": { "attribute": "values", "operator": "includesAny", "value": [1e30] },
+ "context": { "values": [1e30] },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: large finite numbers",
+ "condition": { "attribute": "values", "operator": "notIncludesAny", "value": [1e30] },
+ "context": { "values": [1e30] },
+ "expected": false
+ },
+ {
+ "name": "includes: large actual before a string match",
+ "condition": { "attribute": "values", "operator": "includes", "value": "write" },
+ "context": { "values": [1e30, "write"] },
+ "expected": true
+ },
+ {
+ "name": "includesAny: large actual before a string match",
+ "condition": { "attribute": "values", "operator": "includesAny", "value": ["write"] },
+ "context": { "values": [1e30, "write"] },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: large actual before a string match",
+ "condition": { "attribute": "values", "operator": "notIncludesAny", "value": ["write"] },
+ "context": { "values": [1e30, "write"] },
+ "expected": false
+ },
+ {
+ "name": "includesAny: large candidate before a string match",
+ "condition": { "attribute": "values", "operator": "includesAny", "value": [1e30, "write"] },
+ "context": { "values": ["write"] },
+ "expected": true
+ },
+ {
+ "name": "equals: tiny finite values do not equal zero",
+ "condition": { "attribute": "values", "operator": "equals", "value": 1e-100 },
+ "context": { "values": 0 },
+ "expected": false
+ },
+ {
+ "name": "equals: tiny finite values",
+ "condition": { "attribute": "values", "operator": "equals", "value": 1e-100 },
+ "context": { "values": 1e-100 },
+ "expected": true
+ },
+ {
+ "name": "includes: tiny finite values do not equal zero",
+ "condition": { "attribute": "values", "operator": "includes", "value": 1e-100 },
+ "context": { "values": [0] },
+ "expected": false
+ },
+ {
+ "name": "includesAny: tiny finite values do not equal zero",
+ "condition": { "attribute": "values", "operator": "includesAny", "value": [1e-100] },
+ "context": { "values": [0] },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: tiny finite values do not equal zero",
+ "condition": { "attribute": "values", "operator": "notIncludesAny", "value": [1e-100] },
+ "context": { "values": [0] },
+ "expected": true
+ },
+ {
+ "name": "lessThan: zero is less than a tiny finite value",
+ "condition": { "attribute": "values", "operator": "lessThan", "value": 1e-100 },
+ "context": { "values": 0 },
+ "expected": true
+ },
+ {
+ "name": "greaterThan: tiny finite values exceed zero",
+ "condition": { "attribute": "values", "operator": "greaterThan", "value": 0 },
+ "context": { "values": 1e-100 },
+ "expected": true
+ },
+ {
+ "name": "includesAny: roles hit",
"condition": {
- "attribute": "value",
- "operator": "equals",
- "value": 1
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["admin", "owner"]
+ },
+ "context": {
+ "values": ["member", "owner"]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: roles hit",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["admin", "owner"]
+ },
+ "context": {
+ "values": ["member", "owner"]
},
- "context": { "value": "1" },
"expected": false
},
{
- "name": "not negates implicit and",
+ "name": "includesAny: roles miss",
"condition": {
- "not": [
- { "attribute": "country", "operator": "equals", "value": "us" },
- { "attribute": "device", "operator": "equals", "value": "mobile" }
- ]
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["admin", "owner"]
+ },
+ "context": {
+ "values": ["viewer"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: roles miss",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["admin", "owner"]
+ },
+ "context": {
+ "values": ["viewer"]
},
- "context": { "country": "us", "device": "desktop" },
"expected": true
},
{
- "name": "not with nested or means none match",
+ "name": "includesAny: empty actual",
"condition": {
- "not": [
- {
- "or": [
- { "attribute": "country", "operator": "equals", "value": "us" },
- { "attribute": "country", "operator": "equals", "value": "nl" }
- ]
- }
- ]
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": []
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: empty actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": []
},
- "context": { "country": "de" },
"expected": true
},
{
- "name": "empty not fails defensively",
- "condition": { "not": [] },
- "context": {},
+ "name": "includesAny: empty candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": []
+ },
+ "context": {
+ "values": ["owner"]
+ },
"expected": false
},
{
- "name": "fractional ISO date with offset",
+ "name": "notIncludesAny: empty candidates",
"condition": {
- "attribute": "date",
- "operator": "before",
- "value": "2024-01-01T00:00:00.500Z"
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": []
+ },
+ "context": {
+ "values": ["owner"]
},
- "context": { "date": "2024-01-01T01:00:00.250+01:00" },
"expected": true
- }
- ],
- "childInstances": {
- "contextModel": "snapshot existing parent keys at spawn, inherit newly introduced parent keys, child keys win",
- "stickyStateModel": "child sticky features and variables replace parent sticky state; omitted child sticky options mean empty sticky state",
- "closeRemovesLocalAndDelegatedSubscriptions": true,
- "detailedEvaluationMethods": ["flag", "variation", "variable"],
- "contextCase": {
- "parentAtSpawn": { "country": "nl", "plan": "free" },
- "child": { "country": "de" },
- "parentAfterSpawn": { "country": "us", "plan": "pro", "region": "eu" },
- "expected": { "country": "de", "plan": "free", "region": "eu" }
},
- "stickyCase": {
- "datafile": {
- "schemaVersion": "2",
- "revision": "child-sticky",
- "segments": {},
- "features": {
- "flag": {
- "key": "flag",
- "bucketBy": "userId",
- "traffic": []
- }
- },
- "variables": {
- "setting": {
- "type": "string",
- "defaultValue": "datafile"
- }
- }
+ {
+ "name": "includesAny: both arrays empty",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": []
},
- "parentStickyFeatures": { "flag": { "enabled": true } },
- "parentStickyVariables": { "setting": "parent-sticky" },
- "expectedParent": { "flag": true, "setting": "parent-sticky" },
- "expectedChildWithoutStickyOptions": { "flag": false, "setting": "datafile" }
+ "context": {
+ "values": []
+ },
+ "expected": false
},
- "globalJsonCase": {
- "datafile": {
- "schemaVersion": "2",
+ {
+ "name": "notIncludesAny: both arrays empty",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": []
+ },
+ "context": {
+ "values": []
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: numeric fractional match",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [1.5]
+ },
+ "context": {
+ "values": [0, 1.5]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: numeric fractional match",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [1.5]
+ },
+ "context": {
+ "values": [0, 1.5]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: numeric string distinction",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["1"]
+ },
+ "context": {
+ "values": [1]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: numeric string distinction",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["1"]
+ },
+ "context": {
+ "values": [1]
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: boolean match",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [false]
+ },
+ "context": {
+ "values": [true, false]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: boolean match",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [false]
+ },
+ "context": {
+ "values": [true, false]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: boolean is not one",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [1]
+ },
+ "context": {
+ "values": [true]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: boolean is not one",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [1]
+ },
+ "context": {
+ "values": [true]
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: false is not zero",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [0]
+ },
+ "context": {
+ "values": [false]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: false is not zero",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [0]
+ },
+ "context": {
+ "values": [false]
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: null element",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [null]
+ },
+ "context": {
+ "values": [null, "owner"]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: null element",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [null]
+ },
+ "context": {
+ "values": [null, "owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: null differs from empty text",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [""]
+ },
+ "context": {
+ "values": [null]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: null differs from empty text",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [""]
+ },
+ "context": {
+ "values": [null]
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: duplicates and order",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["absent", "owner", "owner"]
+ },
+ "context": {
+ "values": ["owner", "owner"]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: duplicates and order",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["absent", "owner", "owner"]
+ },
+ "context": {
+ "values": ["owner", "owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: structured actual elements ignored",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": [
+ {
+ "role": "owner"
+ }
+ ]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: structured actual elements ignored",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": [
+ {
+ "role": "owner"
+ }
+ ]
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: structured and primitive actual elements",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": [
+ {
+ "role": "owner"
+ },
+ "owner"
+ ]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: structured and primitive actual elements",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": [
+ {
+ "role": "owner"
+ },
+ "owner"
+ ]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: mixed primitive arrays",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["missing", null]
+ },
+ "context": {
+ "values": [true, 1, "1", null]
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: mixed primitive arrays",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["missing", null]
+ },
+ "context": {
+ "values": [true, 1, "1", null]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: case sensitive",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": ["Owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: case sensitive",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": ["Owner"]
+ },
+ "expected": true
+ },
+ {
+ "name": "includesAny: null actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": null
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: null actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": null
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: missing actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {},
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: missing actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {},
+ "expected": false
+ },
+ {
+ "name": "includesAny: string actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": "owner"
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: string actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": "owner"
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: number actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [1]
+ },
+ "context": {
+ "values": 1
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: number actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [1]
+ },
+ "context": {
+ "values": 1
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: boolean actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [false]
+ },
+ "context": {
+ "values": false
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: boolean actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [false]
+ },
+ "context": {
+ "values": false
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: object actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": {
+ "owner": true
+ }
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: object actual",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "values": {
+ "owner": true
+ }
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: null candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": null
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: null candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": null
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: missing candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny"
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: missing candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny"
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: scalar candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": "owner"
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: scalar candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": "owner"
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: object candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": {
+ "owner": true
+ }
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: object candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": {
+ "owner": true
+ }
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: nested array candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [["owner"]]
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: nested array candidates",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [["owner"]]
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: malformed candidates after a match",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": ["owner", {}]
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: malformed candidates after a match",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": ["owner", {}]
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: malformed candidates before a match",
+ "condition": {
+ "attribute": "values",
+ "operator": "includesAny",
+ "value": [[], "owner"]
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "notIncludesAny: malformed candidates before a match",
+ "condition": {
+ "attribute": "values",
+ "operator": "notIncludesAny",
+ "value": [[], "owner"]
+ },
+ "context": {
+ "values": ["owner"]
+ },
+ "expected": false
+ },
+ {
+ "name": "includesAny: nested object path",
+ "condition": {
+ "attribute": "account.permissions",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "account": {
+ "permissions": ["owner"]
+ }
+ },
+ "expected": true
+ },
+ {
+ "name": "notIncludesAny: nested object path",
+ "condition": {
+ "attribute": "account.permissions",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ },
+ "context": {
+ "account": {
+ "permissions": ["owner"]
+ }
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: empty string",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": ""
+ },
+ "expected": true
+ },
+ {
+ "name": "notEmpty: empty string",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": ""
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: whitespace",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": " \t"
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: whitespace",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": " \t"
+ },
+ "expected": true
+ },
+ {
+ "name": "empty: text",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": "hello"
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: text",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": "hello"
+ },
+ "expected": true
+ },
+ {
+ "name": "empty: empty array",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": []
+ },
+ "expected": true
+ },
+ {
+ "name": "notEmpty: empty array",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": []
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: array containing null",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": [null]
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: array containing null",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": [null]
+ },
+ "expected": true
+ },
+ {
+ "name": "empty: array containing empty array",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": [[]]
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: array containing empty array",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": [[]]
+ },
+ "expected": true
+ },
+ {
+ "name": "empty: empty object",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": {}
+ },
+ "expected": true
+ },
+ {
+ "name": "notEmpty: empty object",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": {}
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: object containing null",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": {
+ "value": null
+ }
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: object containing null",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": {
+ "value": null
+ }
+ },
+ "expected": true
+ },
+ {
+ "name": "empty: object containing empty object",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": {
+ "nested": {}
+ }
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: object containing empty object",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": {
+ "nested": {}
+ }
+ },
+ "expected": true
+ },
+ {
+ "name": "empty: null",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": null
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: null",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": null
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: zero",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": 0
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: zero",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": 0
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: false",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {
+ "value": false
+ },
+ "expected": false
+ },
+ {
+ "name": "notEmpty: false",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {
+ "value": false
+ },
+ "expected": false
+ },
+ {
+ "name": "empty: missing",
+ "condition": {
+ "attribute": "value",
+ "operator": "empty"
+ },
+ "context": {},
+ "expected": false
+ },
+ {
+ "name": "notEmpty: missing",
+ "condition": {
+ "attribute": "value",
+ "operator": "notEmpty"
+ },
+ "context": {},
+ "expected": false
+ },
+ {
+ "name": "strict primitive equality",
+ "condition": {
+ "attribute": "value",
+ "operator": "equals",
+ "value": 1
+ },
+ "context": { "value": "1" },
+ "expected": false
+ },
+ {
+ "name": "not negates implicit and",
+ "condition": {
+ "not": [
+ { "attribute": "country", "operator": "equals", "value": "us" },
+ { "attribute": "device", "operator": "equals", "value": "mobile" }
+ ]
+ },
+ "context": { "country": "us", "device": "desktop" },
+ "expected": true
+ },
+ {
+ "name": "not with nested or means none match",
+ "condition": {
+ "not": [
+ {
+ "or": [
+ { "attribute": "country", "operator": "equals", "value": "us" },
+ { "attribute": "country", "operator": "equals", "value": "nl" }
+ ]
+ }
+ ]
+ },
+ "context": { "country": "de" },
+ "expected": true
+ },
+ {
+ "name": "empty not fails defensively",
+ "condition": { "not": [] },
+ "context": {},
+ "expected": false
+ },
+ {
+ "name": "fractional ISO date with offset",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.500Z"
+ },
+ "context": { "date": "2024-01-01T01:00:00.250+01:00" },
+ "expected": true
+ },
+ {
+ "name": "date rejects February 30",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-30T00:00:00Z" },
+ "expected": false
+ },
+ {
+ "name": "date rejects non leap century",
+ "condition": { "attribute": "date", "operator": "before", "value": "1900-03-01T00:00:00Z" },
+ "context": { "date": "1900-02-29T00:00:00Z" },
+ "expected": false
+ },
+ {
+ "name": "date accepts leap century",
+ "condition": { "attribute": "date", "operator": "before", "value": "2000-03-01T00:00:00Z" },
+ "context": { "date": "2000-02-29T23:59:59Z" },
+ "expected": true
+ },
+ {
+ "name": "date rejects non ISO form",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-29 23:59:59Z" },
+ "expected": false
+ },
+ {
+ "name": "date rejects date only",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-29" },
+ "expected": false
+ },
+ {
+ "name": "date rejects trailing text",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-29T00:00:00Zextra" },
+ "expected": false
+ },
+ {
+ "name": "date rejects 24 hour rollover",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:01Z" },
+ "context": { "date": "2024-02-29T24:00:00Z" },
+ "expected": false
+ },
+ {
+ "name": "date rejects leap second",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:01Z" },
+ "context": { "date": "2024-02-29T23:59:60Z" },
+ "expected": false
+ },
+ {
+ "name": "date accepts four fractional digits",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-01-01T00:00:01Z" },
+ "context": { "date": "2024-01-01T00:00:00.1234Z" },
+ "expected": true
+ },
+ {
+ "name": "date truncates 4 digits before",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.124Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.1234+00:00"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 4 digits after",
+ "condition": {
+ "attribute": "date",
+ "operator": "after",
+ "value": "2024-01-01T00:00:00.123Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.1234+00:00"
+ },
+ "expected": false
+ },
+ {
+ "name": "date accepts 4 digit expected operand",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.1234Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.122Z"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 6 digits before",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.124Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.123456+00:00"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 6 digits after",
+ "condition": {
+ "attribute": "date",
+ "operator": "after",
+ "value": "2024-01-01T00:00:00.123Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.123456+00:00"
+ },
+ "expected": false
+ },
+ {
+ "name": "date accepts 6 digit expected operand",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.123456Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.122Z"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 7 digits before",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.124Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.1234567+00:00"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 7 digits after",
+ "condition": {
+ "attribute": "date",
+ "operator": "after",
+ "value": "2024-01-01T00:00:00.123Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.1234567+00:00"
+ },
+ "expected": false
+ },
+ {
+ "name": "date accepts 7 digit expected operand",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.1234567Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.122Z"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 9 digits before",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.124Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.123456789+00:00"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates 9 digits after",
+ "condition": {
+ "attribute": "date",
+ "operator": "after",
+ "value": "2024-01-01T00:00:00.123Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.123456789+00:00"
+ },
+ "expected": false
+ },
+ {
+ "name": "date accepts 9 digit expected operand",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.123456789Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.122Z"
+ },
+ "expected": true
+ },
+ {
+ "name": "date truncates pre epoch fractions without rounding",
+ "condition": {
+ "attribute": "date",
+ "operator": "after",
+ "value": "1969-12-31T23:59:59.999Z"
+ },
+ "context": {
+ "date": "1969-12-31T23:59:59.999999999Z"
+ },
+ "expected": false
+ },
+ {
+ "name": "date rejects ten fractional digits",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:01Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:00.1234567890Z"
+ },
+ "expected": false
+ },
+ {
+ "name": "date rejects ten digit expected fraction",
+ "condition": {
+ "attribute": "date",
+ "operator": "after",
+ "value": "2024-01-01T00:00:00.1234567890Z"
+ },
+ "context": {
+ "date": "2024-01-01T00:00:01Z"
+ },
+ "expected": false
+ },
+ {
+ "name": "date rejects Unicode digits",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2025-01-01T00:00:00Z"
+ },
+ "context": {
+ "date": "٢٠٢٤-01-01T00:00:00Z"
+ },
+ "expected": false
+ },
+ {
+ "name": "date accepts one fractional digit",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.101Z"
+ },
+ "context": { "date": "2024-01-01T00:00:00.1Z" },
+ "expected": true
+ },
+ {
+ "name": "date compares at millisecond precision",
+ "condition": {
+ "attribute": "date",
+ "operator": "before",
+ "value": "2024-01-01T00:00:00.100Z"
+ },
+ "context": { "date": "2024-01-01T00:00:00.1Z" },
+ "expected": false
+ },
+ {
+ "name": "date rejects offset above fourteen hours",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-29T00:00:00+14:01" },
+ "expected": false
+ },
+ {
+ "name": "date accepts positive fourteen hour offset",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-01-01T00:00:01Z" },
+ "context": { "date": "2024-01-01T14:00:00+14:00" },
+ "expected": true
+ },
+ {
+ "name": "date accepts negative fourteen hour offset",
+ "condition": { "attribute": "date", "operator": "after", "value": "2024-01-01T13:59:59Z" },
+ "context": { "date": "2024-01-01T00:00:00-14:00" },
+ "expected": true
+ },
+ {
+ "name": "date rejects offset minute rollover",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-29T00:00:00+01:60" },
+ "expected": false
+ },
+ {
+ "name": "date rejects year zero",
+ "condition": { "attribute": "date", "operator": "before", "value": "0001-01-01T00:00:01Z" },
+ "context": { "date": "0000-12-31T23:59:59Z" },
+ "expected": false
+ },
+ {
+ "name": "date rejects year zero even when UTC is year one",
+ "condition": { "attribute": "date", "operator": "before", "value": "0001-01-01T00:00:01Z" },
+ "context": { "date": "0000-12-31T23:00:00-01:00" },
+ "expected": false
+ },
+ {
+ "name": "date rejects offset crossing minimum UTC year",
+ "condition": { "attribute": "date", "operator": "before", "value": "0001-01-01T00:00:01Z" },
+ "context": { "date": "0001-01-01T00:00:00+01:00" },
+ "expected": false
+ },
+ {
+ "name": "date accepts maximum UTC year",
+ "condition": { "attribute": "date", "operator": "after", "value": "9999-12-31T23:59:58Z" },
+ "context": { "date": "9999-12-31T23:59:59.999Z" },
+ "expected": true
+ },
+ {
+ "name": "date rejects offset crossing maximum UTC year",
+ "condition": { "attribute": "date", "operator": "after", "value": "9999-12-31T23:59:58Z" },
+ "context": { "date": "9999-12-31T23:59:59-00:01" },
+ "expected": false
+ },
+ {
+ "name": "date rejects trailing newline",
+ "condition": { "attribute": "date", "operator": "before", "value": "2024-03-01T00:00:00Z" },
+ "context": { "date": "2024-02-29T00:00:00Z\n" },
+ "expected": false
+ },
+ {
+ "name": "regex digit is ASCII only",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\d+$" },
+ "context": { "value": "٣" },
+ "expected": false
+ },
+ {
+ "name": "regex nondigit accepts Arabic digit",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\D+$" },
+ "context": { "value": "٣" },
+ "expected": true
+ },
+ {
+ "name": "regex word is ASCII only",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\w+$" },
+ "context": { "value": "é" },
+ "expected": false
+ },
+ {
+ "name": "regex nonword accepts accented letter",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\W+$" },
+ "context": { "value": "é" },
+ "expected": true
+ },
+ {
+ "name": "regex digit class is ASCII only",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\d]+$" },
+ "context": { "value": "٣" },
+ "expected": false
+ },
+ {
+ "name": "regex word class is ASCII only",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\w]+$" },
+ "context": { "value": "é" },
+ "expected": false
+ },
+ {
+ "name": "regex nondigit class accepts Arabic digit",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\D]+$" },
+ "context": { "value": "٣" },
+ "expected": true
+ },
+ {
+ "name": "regex nonword class accepts accented letter",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\W]+$" },
+ "context": { "value": "é" },
+ "expected": true
+ },
+ {
+ "name": "regex single nondigit accepts Arabic digit",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\D$" },
+ "context": { "value": "٣" },
+ "expected": true
+ },
+ {
+ "name": "regex single nonword accepts accented letter",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\W$" },
+ "context": { "value": "é" },
+ "expected": true
+ },
+ {
+ "name": "regex single nondigit class accepts Arabic digit",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\D]$" },
+ "context": { "value": "٣" },
+ "expected": true
+ },
+ {
+ "name": "regex single nonword class accepts accented letter",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\W]$" },
+ "context": { "value": "é" },
+ "expected": true
+ },
+ {
+ "name": "regex negated nondigit class accepts ASCII digit",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[^\\D]$" },
+ "context": { "value": "5" },
+ "expected": true
+ },
+ {
+ "name": "regex negated nondigit class rejects Arabic digit",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[^\\D]$" },
+ "context": { "value": "٣" },
+ "expected": false
+ },
+ {
+ "name": "regex negated nonword class accepts ASCII letter",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[^\\W]$" },
+ "context": { "value": "A" },
+ "expected": true
+ },
+ {
+ "name": "regex negated nonword class rejects accented letter",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[^\\W]$" },
+ "context": { "value": "é" },
+ "expected": false
+ },
+ {
+ "name": "regex escaped backslash inside class is not digit shorthand",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\\\d]$" },
+ "context": { "value": "3" },
+ "expected": false
+ },
+ {
+ "name": "regex escaped backslash inside class remains literal",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^[\\\\d]$" },
+ "context": { "value": "\\" },
+ "expected": true
+ },
+ {
+ "name": "regex escaped literal backslash remains literal",
+ "condition": { "attribute": "value", "operator": "matches", "value": "^\\\\d$" },
+ "context": { "value": "\\d" },
+ "expected": true
+ },
+ {
+ "name": "regex notMatches respects ASCII word",
+ "condition": { "attribute": "value", "operator": "notMatches", "value": "^\\w+$" },
+ "context": { "value": "é" },
+ "expected": true
+ },
+ {
+ "name": "regex word ignores Kelvin sign under i",
+ "condition": {
+ "attribute": "value",
+ "operator": "matches",
+ "value": "^\\w+$",
+ "regexFlags": "i"
+ },
+ "context": { "value": "K" },
+ "expected": false
+ },
+ {
+ "name": "regex word ignores long s under i",
+ "condition": {
+ "attribute": "value",
+ "operator": "matches",
+ "value": "^\\w+$",
+ "regexFlags": "i"
+ },
+ "context": { "value": "ſ" },
+ "expected": false
+ }
+ ],
+ "childInstances": {
+ "contextModel": "snapshot existing parent keys at spawn, inherit newly introduced parent keys, child keys win",
+ "stickyStateModel": "child sticky features and variables replace parent sticky state; omitted child sticky options mean empty sticky state",
+ "closeRemovesLocalAndDelegatedSubscriptions": true,
+ "detailedEvaluationMethods": ["flag", "variation", "variable"],
+ "contextCase": {
+ "parentAtSpawn": { "country": "nl", "plan": "free" },
+ "child": { "country": "de" },
+ "parentAfterSpawn": { "country": "us", "plan": "pro", "region": "eu" },
+ "expected": { "country": "de", "plan": "free", "region": "eu" }
+ },
+ "stickyCase": {
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "child-sticky",
+ "segments": {},
+ "features": {
+ "flag": {
+ "key": "flag",
+ "bucketBy": "userId",
+ "traffic": []
+ }
+ },
+ "variables": {
+ "setting": {
+ "type": "string",
+ "defaultValue": "datafile"
+ }
+ }
+ },
+ "parentStickyFeatures": { "flag": { "enabled": true } },
+ "parentStickyVariables": { "setting": "parent-sticky" },
+ "expectedParent": { "flag": true, "setting": "parent-sticky" },
+ "expectedChildWithoutStickyOptions": { "flag": false, "setting": "datafile" }
+ },
+ "globalJsonCase": {
+ "datafile": {
+ "schemaVersion": "2",
"revision": "child-global-json",
"segments": {},
"features": {},
@@ -833,14 +2710,10 @@
},
"modulePipeline": {
"featureOrder": [
- "before:first",
- "before:second",
"beforeEvaluation:first",
"beforeEvaluation:second",
"afterEvaluation:first",
- "afterEvaluation:second",
- "after:first",
- "after:second"
+ "afterEvaluation:second"
],
"globalOrder": [
"beforeEvaluation:first",
@@ -858,9 +2731,264 @@
"stickyVariableDiagnostic": "sticky_variables_set",
"diagnosticBeforeEvent": true
},
+ "requiredVariables": {
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "required-variables",
+ "segments": {},
+ "features": {
+ "sourceFeature": {
+ "bucketBy": "userId",
+ "variablesSchema": {
+ "profile": {
+ "type": "object",
+ "defaultValue": { "plan": { "name": "free" } }
+ }
+ },
+ "force": [{ "segments": "*", "enabled": true }],
+ "traffic": []
+ },
+ "gated": {
+ "bucketBy": "userId",
+ "conditions": [
+ {
+ "variable": "policy",
+ "path": ["access", "enabled"],
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "feature": "sourceFeature",
+ "variable": "profile",
+ "path": ["plan", "name"],
+ "operator": "equals",
+ "value": "pro"
+ }
+ ],
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "featureOverride": {
+ "bucketBy": "userId",
+ "variablesSchema": {
+ "message": { "type": "string", "defaultValue": "default" }
+ },
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "*",
+ "percentage": 100000,
+ "variableOverrides": {
+ "message": [
+ {
+ "key": "required",
+ "conditions": [
+ {
+ "variable": "policy",
+ "path": ["access", "enabled"],
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "feature": "sourceFeature",
+ "variable": "profile",
+ "path": ["plan", "name"],
+ "operator": "equals",
+ "value": "pro"
+ }
+ ],
+ "value": "matched"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "variables": {
+ "policy": {
+ "type": "object",
+ "defaultValue": { "access": { "enabled": false } }
+ },
+ "malformed": { "type": "json", "defaultValue": "{" },
+ "nullGate": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "default",
+ "conditions": [{ "variable": "malformed", "operator": "isNull" }]
+ },
+ "root": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "default",
+ "conditions": [
+ {
+ "variable": "policy",
+ "path": ["access", "enabled"],
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "feature": "sourceFeature",
+ "variable": "profile",
+ "path": ["plan", "name"],
+ "operator": "equals",
+ "value": "pro"
+ }
+ ]
+ },
+ "override": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrides": [
+ {
+ "key": "required",
+ "conditions": [
+ {
+ "variable": "policy",
+ "path": ["access", "enabled"],
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "feature": "sourceFeature",
+ "variable": "profile",
+ "path": ["plan", "name"],
+ "operator": "equals",
+ "value": "pro"
+ }
+ ],
+ "value": "matched"
+ }
+ ]
+ },
+ "accumulated": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "global",
+ "keyPath": ["global"],
+ "conditions": [
+ {
+ "variable": "policy",
+ "path": ["access", "enabled"],
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "operations": [{ "op": "set", "path": ["global"], "value": true }],
+ "onMatch": 1,
+ "onMiss": 1
+ },
+ {
+ "key": "feature",
+ "keyPath": ["feature"],
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "profile",
+ "path": ["plan", "name"],
+ "operator": "equals",
+ "value": "pro"
+ }
+ ],
+ "operations": [{ "op": "set", "path": ["feature"], "value": true }],
+ "onMatch": 2,
+ "onMiss": 2
+ }
+ ]
+ }
+ }
+ },
+ "stickyCases": [
+ {
+ "name": "no sticky sources",
+ "stickyVariables": {},
+ "stickyFeatures": {},
+ "matched": false,
+ "accumulated": {}
+ },
+ {
+ "name": "only global source",
+ "stickyVariables": { "policy": { "access": { "enabled": true } } },
+ "stickyFeatures": {},
+ "matched": false,
+ "accumulated": { "global": true }
+ },
+ {
+ "name": "only feature source",
+ "stickyVariables": {},
+ "stickyFeatures": {
+ "sourceFeature": {
+ "enabled": true,
+ "variables": { "profile": { "plan": { "name": "pro" } } }
+ }
+ },
+ "matched": false,
+ "accumulated": { "feature": true }
+ },
+ {
+ "name": "mixed global and feature sources",
+ "stickyVariables": { "policy": { "access": { "enabled": true } } },
+ "stickyFeatures": {
+ "sourceFeature": {
+ "enabled": true,
+ "variables": { "profile": { "plan": { "name": "pro" } } }
+ }
+ },
+ "matched": true,
+ "accumulated": { "global": true, "feature": true }
+ }
+ ],
+ "groupInputs": [
+ { "a": false, "b": false },
+ { "a": false, "b": true },
+ { "a": true, "b": false },
+ { "a": true, "b": true }
+ ],
+ "groups": [
+ {
+ "name": "implicit AND",
+ "requirements": ["a", "b"],
+ "results": [false, false, false, true]
+ },
+ {
+ "name": "explicit AND",
+ "requirements": [{ "and": ["a", "b"] }],
+ "results": [false, false, false, true]
+ },
+ {
+ "name": "OR",
+ "requirements": [{ "or": ["a", "b"] }],
+ "results": [false, true, true, true]
+ },
+ {
+ "name": "NOT means not all",
+ "requirements": [{ "not": ["a", "b"] }],
+ "results": [true, true, true, false]
+ },
+ {
+ "name": "NOT OR means neither",
+ "requirements": [{ "not": [{ "or": ["a", "b"] }] }],
+ "results": [true, false, false, false]
+ },
+ {
+ "name": "empty NOT defensive fallback",
+ "requirements": [{ "not": [] }],
+ "results": [false, false, false, false]
+ }
+ ],
+ "expected": {
+ "matchedValue": "matched",
+ "defaultValue": "default",
+ "accumulated": { "global": true, "feature": true },
+ "unmetReason": "conditions_unmet",
+ "nullGetterValue": null
+ }
+ },
"openFeature": {
"reasonMappings": {
- "required_features_unmet": "DISABLED"
+ "conditions_unmet": "DISABLED"
}
},
"diagnosticCase": {
diff --git a/conformance/sdk-v4-cases.json b/conformance/sdk-v4-cases.json
new file mode 100644
index 0000000..424a283
--- /dev/null
+++ b/conformance/sdk-v4-cases.json
@@ -0,0 +1,21153 @@
+{
+ "version": 1,
+ "description": "Portable v4 accumulation, mutation, path and error cases supplementing sdk-v3.json version 11. Run each case twice through the ordinary global variable getter and detailed evaluator. Do not mutate fixture datafiles. Extra evaluation metadata is allowed; compare expected fields only.",
+ "cases": [
+ {
+ "name": "empty global requirement: empty string",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": ""
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty feature requirement: empty string",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": ""
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty program condition: empty string",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": ""
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: empty string",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": ""
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: empty string",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": ""
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: empty string",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": ""
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty global requirement: whitespace",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": " \t"
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: whitespace",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": " \t"
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: whitespace",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": " \t"
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: whitespace",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": " \t"
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: whitespace",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": " \t"
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty program condition: whitespace",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": " \t"
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "empty global requirement: text",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": "hello"
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: text",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": "hello"
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: text",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": "hello"
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: text",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": "hello"
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: text",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": "hello"
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty program condition: text",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": "hello"
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "empty global requirement: empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": []
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty feature requirement: empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": []
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty program condition: empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": []
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": []
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": []
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": []
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty global requirement: array containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ null
+ ]
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: array containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ null
+ ]
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: array containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": [
+ null
+ ]
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: array containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ null
+ ]
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: array containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ null
+ ]
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty program condition: array containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": [
+ null
+ ]
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "empty global requirement: array containing empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ []
+ ]
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: array containing empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ []
+ ]
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: array containing empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": [
+ []
+ ]
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: array containing empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ []
+ ]
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: array containing empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": [
+ []
+ ]
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty program condition: array containing empty array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": [
+ []
+ ]
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "empty global requirement: empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {}
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty feature requirement: empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {}
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty program condition: empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": {}
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {}
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {}
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": {}
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty global requirement: object containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "value": null
+ }
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: object containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "value": null
+ }
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: object containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": {
+ "value": null
+ }
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: object containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "value": null
+ }
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: object containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "value": null
+ }
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty program condition: object containing null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": {
+ "value": null
+ }
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "empty global requirement: object containing empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "nested": {}
+ }
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: object containing empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "nested": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: object containing empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": {
+ "nested": {}
+ }
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: object containing empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "nested": {}
+ }
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: object containing empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": {
+ "nested": {}
+ }
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": true,
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty program condition: object containing empty object",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": {
+ "nested": {}
+ }
+ },
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_override_rule"
+ }
+ },
+ {
+ "name": "empty global requirement: null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": null
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": null
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": null
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": null
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": null
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": null
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty global requirement: zero",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": 0
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: zero",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": 0
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: zero",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": 0
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: zero",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": 0
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: zero",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": 0
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: zero",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": 0
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty global requirement: false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": false
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": false
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": false
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": false
+ }
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "value": false
+ }
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "value": false
+ },
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty global requirement: missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {}
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty feature requirement: missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {}
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "empty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "empty program condition: missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "empty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "notEmpty global requirement: missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {}
+ },
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty feature requirement: missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {
+ "sourceFeature": {
+ "bucketBy": [
+ "userId"
+ ],
+ "force": [
+ {
+ "segments": "*",
+ "enabled": true
+ }
+ ],
+ "traffic": [],
+ "variablesSchema": {
+ "source": {
+ "type": "object",
+ "defaultValue": {}
+ }
+ }
+ }
+ },
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "boolean",
+ "defaultValue": true,
+ "disabledValue": false,
+ "conditions": [
+ {
+ "feature": "sourceFeature",
+ "variable": "source",
+ "path": [
+ "value"
+ ],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": false,
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "notEmpty program condition: missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "emptiness",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [
+ {
+ "key": "content",
+ "keyPath": [
+ "content"
+ ],
+ "conditions": [
+ {
+ "attribute": "value",
+ "operator": "notEmpty"
+ }
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": "matched"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "set root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "old": true
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "next": true
+ }
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "next": true
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "set missing own property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "enabled"
+ ],
+ "value": false
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "enabled": false
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "literal dotted property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "a.b": {
+ "value": 1
+ }
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "a.b",
+ "value"
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "a.b": {
+ "value": 2
+ }
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "numeric string object property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "0": 1
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "0"
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "0": 2
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "set at array length",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ 1
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1,
+ 2
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "set beyond array length",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ 2
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "set selected item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": 1
+ },
+ {
+ "id": 2
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 2
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 3
+ }
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": 1
+ },
+ {
+ "id": 3
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "remove own property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "one": 1,
+ "two": 2
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": [
+ "one"
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "two": 2
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "remove array item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1,
+ 2,
+ 3
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": [
+ 1
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1,
+ 3
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "remove missing property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": [
+ "one"
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {},
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "a"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "a",
+ "x"
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": [
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "a",
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ [
+ "a"
+ ]
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ [
+ "a",
+ "x"
+ ]
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append selected nested array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "a"
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "a",
+ "tags": [
+ "a",
+ "x"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append null root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": null,
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append null array element",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ null
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ null
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append missing final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append null final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": null
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append missing intermediate",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {},
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append unavailable array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append wrong container",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": false
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "prepend root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "a"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "x",
+ "a"
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": [
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x",
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ [
+ "a"
+ ]
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ [
+ "x",
+ "a"
+ ]
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend selected nested array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "a"
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "a",
+ "tags": [
+ "x",
+ "a"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend null root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": null,
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend null array element",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ null
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ null
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend missing final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend null final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": null
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend missing intermediate",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {},
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend unavailable array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend wrong container",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": false
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "appendUnique root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "a"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "a",
+ "x"
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": [
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "a",
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ [
+ "a"
+ ]
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ [
+ "a",
+ "x"
+ ]
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique selected nested array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "a"
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "a",
+ "tags": [
+ "a",
+ "x"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique null root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": null,
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique null array element",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ null
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ null
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique missing final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique null final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": null
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "x"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique missing intermediate",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {},
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique unavailable array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique wrong container",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": false
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "removeValue root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "a",
+ "x",
+ "x"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "a"
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": [
+ "a",
+ "x",
+ "x"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ [
+ "a",
+ "x",
+ "x"
+ ]
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ [
+ "a"
+ ]
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue selected nested array",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "a",
+ "tags": [
+ "a",
+ "x",
+ "x"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a",
+ "x",
+ "x"
+ ]
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "a"
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "a",
+ "tags": [
+ "a"
+ ]
+ },
+ {
+ "id": "a",
+ "tags": [
+ "a",
+ "x",
+ "x"
+ ]
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue null root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": null,
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue null array element",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ null
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ null
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue missing final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {},
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue null final property",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": null
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "tags": null
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue missing intermediate",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {},
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue unavailable array index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ 0
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue wrong container",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "tags": false
+ },
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "tags"
+ ],
+ "value": "x"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "appendUnique strict \"1\"",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ "1"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": "1"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ "1"
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue strict \"1\"",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ "1"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": "1"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1,
+ true,
+ false,
+ null,
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique strict 1",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": 1
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ 1
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue strict 1",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": 1
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ true,
+ false,
+ null,
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique strict true",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ true
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ true
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue strict true",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ true
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ false,
+ null,
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique strict false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ false
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": false
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ false
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue strict false",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ false
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": false
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ null,
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique strict null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ null
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": null
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ null
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue strict null",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ null
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": null
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique strict 0",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ 0
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": 0
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "removeValue strict 0",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ 1,
+ true,
+ false,
+ null,
+ 0,
+ 0
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": 0
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ 1,
+ true,
+ false,
+ null
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "unique append preserves existing duplicates",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "1",
+ "1"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": 1
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "1",
+ "1",
+ 1
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "zero and negative zero compare equally",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 0
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": 0
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 0
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "append inserts an array as one item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [],
+ "value": [
+ 2,
+ 3
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1,
+ [
+ 2,
+ 3
+ ]
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "prepend inserts an array as one item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "prepend",
+ "path": [],
+ "value": [
+ 2,
+ 3
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ [
+ 2,
+ 3
+ ],
+ 1
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "before indexed item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1,
+ 3
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ 1
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1,
+ 2,
+ 3
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "before selected item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "a"
+ },
+ {
+ "id": "b"
+ },
+ {
+ "id": "b"
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "b"
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": "new"
+ }
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "a"
+ },
+ {
+ "id": "new"
+ },
+ {
+ "id": "b"
+ },
+ {
+ "id": "b"
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "before unavailable index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ 1
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "before invalid root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "before",
+ "path": [],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "after indexed item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1,
+ 3
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "after",
+ "path": [
+ 1
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1,
+ 3,
+ 2
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "after selected item",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "a"
+ },
+ {
+ "id": "b"
+ },
+ {
+ "id": "b"
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "after",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "b"
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": "new"
+ }
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "a"
+ },
+ {
+ "id": "b"
+ },
+ {
+ "id": "new"
+ },
+ {
+ "id": "b"
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "after unavailable index",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ 1
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "after",
+ "path": [
+ 1
+ ],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ 1
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "after invalid root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "after",
+ "path": [],
+ "value": 2
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "selector distinguishes null from missing",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "other": 1
+ },
+ {
+ "id": null
+ },
+ {
+ "id": null
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ null
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "other": 1
+ },
+ {
+ "id": null
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "selector distinguishes booleans from numbers",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": 1
+ },
+ {
+ "id": true
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ true
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": 1
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "selector candidates can include text and number",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "01"
+ },
+ {
+ "id": 1
+ },
+ {
+ "id": "1"
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "1",
+ 1
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "01"
+ },
+ {
+ "id": "1"
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "selectors see previous mutations",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ {
+ "id": "old"
+ }
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [],
+ "value": {
+ "id": "new",
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "new"
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "seen"
+ },
+ {
+ "op": "remove",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ "old"
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ {
+ "id": "new",
+ "tags": [
+ "seen"
+ ]
+ }
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "ordered idempotent primitive operations",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [
+ "a",
+ "b",
+ "b"
+ ],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": "b"
+ },
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": "b"
+ },
+ {
+ "op": "appendUnique",
+ "path": [],
+ "value": "b"
+ },
+ {
+ "op": "removeValue",
+ "path": [],
+ "value": "a"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": [
+ "b"
+ ],
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "case"
+ ]
+ ],
+ "variableOverrideKey": "case",
+ "variableOverrideIndex": 0,
+ "variableOverridePath": [
+ "case"
+ ]
+ }
+ },
+ {
+ "name": "appendUnique invalid operand {} even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": {}
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "appendUnique invalid operand [] even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "appendUnique",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": []
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "removeValue invalid operand {} even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": {}
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "removeValue invalid operand [] even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "removeValue",
+ "path": [
+ "missing",
+ "tags"
+ ],
+ "value": []
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unsafe property __proto__",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "__proto__"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unsafe selector __proto__ even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ {
+ "select": {
+ "property": "__proto__",
+ "values": [
+ "missing"
+ ]
+ }
+ }
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unsafe property prototype",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "prototype"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unsafe selector prototype even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ {
+ "select": {
+ "property": "prototype",
+ "values": [
+ "missing"
+ ]
+ }
+ }
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unsafe property constructor",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "constructor"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unsafe selector constructor even on a miss",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ {
+ "select": {
+ "property": "constructor",
+ "values": [
+ "missing"
+ ]
+ }
+ }
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid path [-1]",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ -1
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid path [0.5]",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ 0.5
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid path [true]",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ true
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid path [null]",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ null
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid path [{\"select\":{\"property\":\"id\",\"values\":[{}]}}]",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "array",
+ "defaultValue": [],
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ {}
+ ]
+ }
+ }
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "missing operand",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "a"
+ ]
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "unknown operation",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "unknown",
+ "path": [],
+ "value": 1
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "cannot remove root",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": []
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "string paths are not parsed by SDK",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": "a.b",
+ "value": 1
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "late operation error never publishes partial result",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "a"
+ ],
+ "value": true
+ },
+ {
+ "op": "append",
+ "path": [
+ "a"
+ ],
+ "value": 1
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "nested continuation eu nl",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "trace": []
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "parent"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 3,
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ {
+ "key": "nl",
+ "keyPath": [
+ "parent",
+ "nl"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "nl"
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 2,
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "child"
+ }
+ ],
+ "onMatch": 3,
+ "onMiss": 3,
+ "segments": "*"
+ },
+ {
+ "key": "tail",
+ "keyPath": [
+ "tail"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "tail"
+ }
+ ],
+ "onMatch": 4,
+ "onMiss": 4,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "trace": [
+ "parent",
+ "nl",
+ "child",
+ "tail"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "nl"
+ ],
+ [
+ "parent",
+ "child"
+ ],
+ [
+ "tail"
+ ]
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "nested continuation eu be",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "trace": []
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "parent"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 3,
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ {
+ "key": "nl",
+ "keyPath": [
+ "parent",
+ "nl"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "nl"
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 2,
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "child"
+ }
+ ],
+ "onMatch": 3,
+ "onMiss": 3,
+ "segments": "*"
+ },
+ {
+ "key": "tail",
+ "keyPath": [
+ "tail"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "tail"
+ }
+ ],
+ "onMatch": 4,
+ "onMiss": 4,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "trace": [
+ "parent",
+ "child",
+ "tail"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ],
+ [
+ "tail"
+ ]
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "nested continuation us nl",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "trace": []
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "parent"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 3,
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ {
+ "key": "nl",
+ "keyPath": [
+ "parent",
+ "nl"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "nl"
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 2,
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "child"
+ }
+ ],
+ "onMatch": 3,
+ "onMiss": 3,
+ "segments": "*"
+ },
+ {
+ "key": "tail",
+ "keyPath": [
+ "tail"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "tail"
+ }
+ ],
+ "onMatch": 4,
+ "onMiss": 4,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "nl"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "trace": [
+ "tail"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "tail"
+ ]
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "nested continuation us be",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "trace": []
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "parent"
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 3,
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ {
+ "key": "nl",
+ "keyPath": [
+ "parent",
+ "nl"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "nl"
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 2,
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "child"
+ }
+ ],
+ "onMatch": 3,
+ "onMiss": 3,
+ "segments": "*"
+ },
+ {
+ "key": "tail",
+ "keyPath": [
+ "tail"
+ ],
+ "operations": [
+ {
+ "op": "append",
+ "path": [
+ "trace"
+ ],
+ "value": "tail"
+ }
+ ],
+ "onMatch": 4,
+ "onMiss": 4,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "be"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "trace": [
+ "tail"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverridePaths": [
+ [
+ "tail"
+ ]
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "invalid forward jump 0",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "partial"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 0,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid forward jump -1",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "partial"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": -1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid forward jump 2",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "partial"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 2,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid forward jump 0.5",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "partial"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": 0.5,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid forward jump \"1\"",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "partial"
+ ],
+ "value": true
+ }
+ ],
+ "onMatch": "1",
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "empty program",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": []
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "empty program ignores ordinary overrides",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "subject": {
+ "type": "string",
+ "defaultValue": "default",
+ "overrideProgram": [],
+ "overrides": [
+ {
+ "key": "unused",
+ "segments": "*",
+ "value": "incorrect"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "default",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required path [\"missing\"] isNull",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "missing"
+ ],
+ "operator": "isNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "required path [\"missing\"] notNull",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "missing"
+ ],
+ "operator": "notNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "required path [\"missing\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "missing"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "required path [\"null\"] isNull",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "null"
+ ],
+ "operator": "isNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required path [\"null\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "null"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required path [\"null\"] notNull",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "null"
+ ],
+ "operator": "notNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "required path [\"false\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "false"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required path [\"zero\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "zero"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required path [\"empty\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "empty"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required path [0] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ 0
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "required path [\"0\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {
+ "0": true,
+ "null": null,
+ "false": false,
+ "zero": 0,
+ "empty": ""
+ }
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "0"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required array path [\"0\"] equals",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "array",
+ "defaultValue": [
+ "first"
+ ]
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "0"
+ ],
+ "operator": "equals",
+ "value": "first"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required array path [0] equals",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "array",
+ "defaultValue": [
+ "first"
+ ]
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ 0
+ ],
+ "operator": "equals",
+ "value": "first"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required array path [\"length\"] equals",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "array",
+ "defaultValue": [
+ "first"
+ ]
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "length"
+ ],
+ "operator": "equals",
+ "value": 1
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "required array path [\"00\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "array",
+ "defaultValue": [
+ "first"
+ ]
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "00"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "required array path [\"-1\"] exists",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "array",
+ "defaultValue": [
+ "first"
+ ]
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "-1"
+ ],
+ "operator": "exists"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "miss",
+ "reason": "conditions_unmet"
+ }
+ },
+ {
+ "name": "getter null after source program error remains a legitimate comparison",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {},
+ "overrideProgram": [
+ {
+ "key": "case",
+ "keyPath": [
+ "case"
+ ],
+ "operations": [
+ {
+ "op": "remove",
+ "path": []
+ }
+ ],
+ "onMatch": 1,
+ "onMiss": 1,
+ "segments": "*"
+ }
+ ]
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "operator": "isNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "JSON getter null remains a legitimate comparison",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "json",
+ "defaultValue": "not JSON"
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "operator": "isNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": "matched",
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "invalid matches cannot succeed through NOT",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "string",
+ "defaultValue": "text"
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "not": [
+ {
+ "variable": "source",
+ "operator": "matches",
+ "value": "["
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid semverGreaterThan cannot succeed through NOT",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "string",
+ "defaultValue": "also-broken"
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "not": [
+ {
+ "variable": "source",
+ "operator": "semverGreaterThan",
+ "value": "broken"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid required path [\"constructor\"]",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {}
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": [
+ "constructor"
+ ],
+ "operator": "notNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "invalid required path \"a.b\"",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "object",
+ "defaultValue": {}
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "source",
+ "path": "a.b",
+ "operator": "notNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "variable cycle cannot pass an isNull check",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {},
+ "segments": {},
+ "variables": {
+ "source": {
+ "type": "boolean",
+ "defaultValue": true
+ },
+ "subject": {
+ "type": "string",
+ "defaultValue": "matched",
+ "disabledValue": "miss",
+ "conditions": [
+ {
+ "variable": "subject",
+ "operator": "isNull"
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": null,
+ "reason": "error"
+ }
+ },
+ {
+ "name": "first match precomputed parsed: child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1
+ }
+ },
+ {
+ "name": "first match precomputed parsed: literal reset",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "reset",
+ "variableOverridePath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "variableOverrideIndex": 0
+ }
+ },
+ {
+ "name": "first match precomputed parsed: parent fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "first match precomputed parsed: sibling fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 2
+ }
+ },
+ {
+ "name": "first match precomputed parsed: unmet feature",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": false,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 2
+ }
+ },
+ {
+ "name": "first match precomputed parsed: unmet variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "denied": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "first match precomputed parsed: parent miss skips matching child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match precomputed parsed: root fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "us"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "us",
+ "variableOverridePath": [
+ "us"
+ ],
+ "variableOverrideIndex": 4
+ }
+ },
+ {
+ "name": "first match precomputed parsed: missing context",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match precomputed parsed: first child prevents later matching sibling",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1
+ }
+ },
+ {
+ "name": "first match precomputed stringified: child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1
+ }
+ },
+ {
+ "name": "first match precomputed stringified: literal reset",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "reset",
+ "variableOverridePath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "variableOverrideIndex": 0
+ }
+ },
+ {
+ "name": "first match precomputed stringified: parent fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "first match precomputed stringified: sibling fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 2
+ }
+ },
+ {
+ "name": "first match precomputed stringified: unmet feature",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": false,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 2
+ }
+ },
+ {
+ "name": "first match precomputed stringified: unmet variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "denied": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 3
+ }
+ },
+ {
+ "name": "first match precomputed stringified: parent miss skips matching child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match precomputed stringified: root fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "us"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "us",
+ "variableOverridePath": [
+ "us"
+ ],
+ "variableOverrideIndex": 4
+ }
+ },
+ {
+ "name": "first match precomputed stringified: missing context",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match precomputed stringified: first child prevents later matching sibling",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrides": [
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "segments": "[\"europe\"]",
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "segments": "[\"europe\"]",
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ }
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1
+ }
+ },
+ {
+ "name": "first match runtime parsed: child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: literal reset",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "reset",
+ "variableOverridePath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "variableOverrideIndex": 2,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ],
+ [
+ "parent",
+ "child",
+ "reset"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: parent fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 0,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: sibling fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 3,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "fallback"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: unmet feature",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": false,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 3,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "fallback"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: unmet variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "denied": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 0,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: parent miss skips matching child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match runtime parsed: root fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "us"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "us",
+ "variableOverridePath": [
+ "us"
+ ],
+ "variableOverrideIndex": 4,
+ "variableOverridePaths": [
+ [
+ "us"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime parsed: missing context",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match runtime parsed: first child prevents later matching sibling",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": [
+ {
+ "attribute": "region",
+ "operator": "equals",
+ "value": "eu"
+ }
+ ]
+ },
+ "eligible": {
+ "conditions": [
+ {
+ "attribute": "eligible",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": [
+ {
+ "attribute": "denied",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": [
+ "europe"
+ ],
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": {
+ "and": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "nl"
+ },
+ {
+ "variable": "permission",
+ "operator": "equals",
+ "value": true
+ }
+ ]
+ },
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": [
+ {
+ "attribute": "reset",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": [
+ {
+ "attribute": "city",
+ "operator": "equals",
+ "value": "rotterdam"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": [
+ {
+ "attribute": "country",
+ "operator": "equals",
+ "value": "us"
+ }
+ ],
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: literal reset",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "reset",
+ "variableOverridePath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "variableOverrideIndex": 2,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ],
+ [
+ "parent",
+ "child",
+ "reset"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: parent fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "eligible": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 0,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: sibling fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "be",
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 3,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "fallback"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: unmet feature",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": false,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "fallback",
+ "rows": [],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "fallback",
+ "variableOverridePath": [
+ "parent",
+ "fallback"
+ ],
+ "variableOverrideIndex": 3,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "fallback"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: unmet variable",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "denied": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 1,
+ "tags": [
+ "parent"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "parent",
+ "variableOverridePath": [
+ "parent"
+ ],
+ "variableOverrideIndex": 0,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: parent miss skips matching child",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "nl",
+ "eligible": true,
+ "reset": true
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match runtime stringified: root fallback",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "us",
+ "country": "us"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "us",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "us",
+ "variableOverridePath": [
+ "us"
+ ],
+ "variableOverrideIndex": 4,
+ "variableOverridePaths": [
+ [
+ "us"
+ ]
+ ]
+ }
+ },
+ {
+ "name": "first match runtime stringified: missing context",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {},
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "reason": "variable_default"
+ }
+ },
+ {
+ "name": "first match runtime stringified: first child prevents later matching sibling",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "portable-v4",
+ "features": {
+ "gate": {
+ "bucketBy": "userId",
+ "traffic": [
+ {
+ "key": "eligible",
+ "segments": [
+ "eligible"
+ ],
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "segments": {
+ "europe": {
+ "conditions": "[{\"attribute\":\"region\",\"operator\":\"equals\",\"value\":\"eu\"}]"
+ },
+ "eligible": {
+ "conditions": "[{\"attribute\":\"eligible\",\"operator\":\"equals\",\"value\":true}]"
+ }
+ },
+ "variables": {
+ "permission": {
+ "type": "boolean",
+ "defaultValue": true,
+ "overrides": [
+ {
+ "key": "denied",
+ "conditions": "[{\"attribute\":\"denied\",\"operator\":\"equals\",\"value\":true}]",
+ "value": false
+ }
+ ]
+ },
+ "subject": {
+ "type": "object",
+ "defaultValue": {
+ "stage": "default",
+ "rows": [
+ {
+ "id": 1,
+ "tags": []
+ }
+ ],
+ "flags": [
+ null,
+ "a"
+ ]
+ },
+ "overrideProgram": [
+ {
+ "key": "parent",
+ "keyPath": [
+ "parent"
+ ],
+ "segments": "[\"europe\"]",
+ "onMatch": 1,
+ "onMiss": 4,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "parent"
+ },
+ {
+ "op": "removeValue",
+ "path": [
+ "flags"
+ ],
+ "value": null
+ },
+ {
+ "op": "appendUnique",
+ "path": [
+ "flags"
+ ],
+ "value": "a"
+ }
+ ]
+ },
+ {
+ "key": "child",
+ "keyPath": [
+ "parent",
+ "child"
+ ],
+ "requiredFeatures": [
+ "gate"
+ ],
+ "conditions": "{\"and\":[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"nl\"},{\"variable\":\"permission\",\"operator\":\"equals\",\"value\":true}]}",
+ "onMatch": 2,
+ "onMiss": 3,
+ "operations": [
+ {
+ "op": "before",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ }
+ ],
+ "value": {
+ "id": 0,
+ "tags": []
+ }
+ },
+ {
+ "op": "append",
+ "path": [
+ "rows",
+ {
+ "select": {
+ "property": "id",
+ "values": [
+ 1
+ ]
+ }
+ },
+ "tags"
+ ],
+ "value": "child"
+ }
+ ]
+ },
+ {
+ "key": "reset",
+ "keyPath": [
+ "parent",
+ "child",
+ "reset"
+ ],
+ "conditions": "[{\"attribute\":\"reset\",\"operator\":\"equals\",\"value\":true}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [],
+ "value": {
+ "stage": "reset",
+ "rows": [],
+ "flags": []
+ }
+ }
+ ]
+ },
+ {
+ "key": "fallback",
+ "keyPath": [
+ "parent",
+ "fallback"
+ ],
+ "conditions": "[{\"attribute\":\"city\",\"operator\":\"equals\",\"value\":\"rotterdam\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "fallback"
+ },
+ {
+ "op": "remove",
+ "path": [
+ "rows",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "key": "us",
+ "keyPath": [
+ "us"
+ ],
+ "conditions": "[{\"attribute\":\"country\",\"operator\":\"equals\",\"value\":\"us\"}]",
+ "onMatch": 5,
+ "onMiss": 5,
+ "operations": [
+ {
+ "op": "set",
+ "path": [
+ "stage"
+ ],
+ "value": "us"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "context": {
+ "region": "eu",
+ "country": "nl",
+ "eligible": true,
+ "city": "rotterdam"
+ },
+ "options": {},
+ "expected": {
+ "value": {
+ "stage": "parent",
+ "rows": [
+ {
+ "id": 0,
+ "tags": []
+ },
+ {
+ "id": 1,
+ "tags": [
+ "parent",
+ "child"
+ ]
+ }
+ ],
+ "flags": [
+ "a"
+ ]
+ },
+ "reason": "variable_override_rule",
+ "variableOverrideKey": "child",
+ "variableOverridePath": [
+ "parent",
+ "child"
+ ],
+ "variableOverrideIndex": 1,
+ "variableOverridePaths": [
+ [
+ "parent"
+ ],
+ [
+ "parent",
+ "child"
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/conformance/semver.json b/conformance/semver.json
new file mode 100644
index 0000000..56e96fb
--- /dev/null
+++ b/conformance/semver.json
@@ -0,0 +1,76 @@
+{
+ "kind": "featurevisor.semver",
+ "version": 1,
+ "description": "Expected ordering is -1, 0 or 1, or null for invalid input. Exercise all six operators in both operand directions through conditions, required variables and CLI checks. Invalid conditions are false; invalid requirements and checks are errors.",
+ "cases": [
+ { "name": "equal", "left": "1.2.3", "right": "1.2.3", "order": 0 },
+ { "name": "major", "left": "2.0.0", "right": "1.9.9", "order": 1 },
+ { "name": "minor", "left": "1.10.0", "right": "1.9.9", "order": 1 },
+ { "name": "patch", "left": "1.0.10", "right": "1.0.9", "order": 1 },
+ { "name": "short major", "left": "1", "right": "1.0.0", "order": 0 },
+ { "name": "short minor", "left": "1.0", "right": "1.0.0", "order": 0 },
+ { "name": "fourth component", "left": "1.2.3.4", "right": "1.2.3.5", "order": -1 },
+ { "name": "missing fourth", "left": "1.2.3", "right": "1.2.3.0", "order": 0 },
+ { "name": "prefix", "left": "v1.2.3", "right": "1.2.3", "order": 0 },
+ { "name": "comparison prefix", "left": ">=1.2.3", "right": "1.2.3", "order": 0 },
+ { "name": "tilde prefix", "left": "~1.2.3", "right": "1.2.3", "order": 0 },
+ { "name": "caret prefix", "left": "^1.2.3", "right": "1.2.3", "order": 0 },
+ { "name": "core wildcard", "left": "1.x.3", "right": "1.9.3", "order": 0 },
+ { "name": "uppercase wildcard", "left": "1.X.3", "right": "1.9.3", "order": 0 },
+ { "name": "star wildcard", "left": "1.2.*", "right": "1.2.42", "order": 0 },
+ { "name": "wildcard keeps later comparison", "left": "1.x.3", "right": "1.9.4", "order": -1 },
+ { "name": "metadata", "left": "1.2.3+build.1", "right": "1.2.3+other.2", "order": 0 },
+ { "name": "leading zeroes", "left": "01.002.0003", "right": "1.2.3", "order": 0 },
+ {
+ "name": "large integers",
+ "left": "9007199254740993.0.0",
+ "right": "9007199254740992.0.0",
+ "order": 1
+ },
+ {
+ "name": "integer overflow",
+ "left": "999999999999999999999.0.0",
+ "right": "1000000000000000000000.0.0",
+ "order": -1
+ },
+ { "name": "release precedence", "left": "1.0.0-alpha", "right": "1.0.0", "order": -1 },
+ { "name": "zero prerelease", "left": "1.0.0-0", "right": "1.0.0", "order": -1 },
+ { "name": "numeric leading identifiers", "left": "1.0.0-1a", "right": "1.0.0-1b", "order": -1 },
+ { "name": "numeric prerelease", "left": "1.0.0-10", "right": "1.0.0-2", "order": 1 },
+ {
+ "name": "numeric before textual prerelease",
+ "left": "1.0.0-10",
+ "right": "1.0.0-1a",
+ "order": -1
+ },
+ { "name": "prerelease length", "left": "1.0.0-alpha", "right": "1.0.0-alpha.0", "order": -1 },
+ { "name": "prerelease zero normalization", "left": "1.0.0-01", "right": "1.0.0-1", "order": 0 },
+ {
+ "name": "prerelease integer overflow",
+ "left": "1.0.0-999999999999999999999",
+ "right": "1.0.0-1000000000000000000000",
+ "order": -1
+ },
+ { "name": "prerelease x is literal", "left": "1.0.0-x", "right": "1.0.0-alpha", "order": 1 },
+ { "name": "prerelease case", "left": "1.0.0-A", "right": "1.0.0-a", "order": -1 },
+ { "name": "fourth with prerelease", "left": "1.2.3.4-alpha", "right": "1.2.3.4", "order": -1 },
+ { "name": "empty", "left": "", "right": "1.0.0", "order": null },
+ { "name": "unicode digit", "left": "٢.0.0", "right": "1.0.0", "order": null },
+ { "name": "unicode prerelease digit", "left": "1.0.0-٢", "right": "1.0.0", "order": null },
+ { "name": "unicode case folding", "left": "1.0.0-K", "right": "1.0.0-k", "order": null },
+ { "name": "multiline", "left": "junk\n1.0.0\njunk", "right": "1.0.0", "order": null },
+ { "name": "trailing newline", "left": "1.0.0\n", "right": "1.0.0", "order": null },
+ { "name": "trailing carriage return", "left": "1.0.0\r\n", "right": "1.0.0", "order": null },
+ { "name": "leading whitespace", "left": " 1.0.0", "right": "1.0.0", "order": null },
+ { "name": "trailing whitespace", "left": "1.0.0 ", "right": "1.0.0", "order": null },
+ { "name": "fifth component", "left": "1.2.3.4.5", "right": "1.0.0", "order": null },
+ { "name": "empty prerelease", "left": "1.0.0-", "right": "1.0.0", "order": null },
+ {
+ "name": "empty prerelease identifier",
+ "left": "1.0.0-a..b",
+ "right": "1.0.0",
+ "order": null
+ },
+ { "name": "short prerelease", "left": "1.0-alpha", "right": "1.0.0", "order": null }
+ ]
+}
diff --git a/conformance/test-checks.json b/conformance/test-checks.json
new file mode 100644
index 0000000..8c29242
--- /dev/null
+++ b/conformance/test-checks.json
@@ -0,0 +1,3495 @@
+{
+ "version": 3,
+ "description": "CLI only. Resolve references with native SDK getters against datafile. Each case uses a fresh instance. error means invalid check or resolution/comparison error, not an ordinary failed predicate.",
+ "datafile": {
+ "schemaVersion": "2",
+ "revision": "cli-checks-v2",
+ "segments": {},
+ "features": {
+ "membershipFeature": {
+ "bucketBy": ["userId"],
+ "variablesSchema": { "roles": { "type": "array", "defaultValue": ["member", "owner"] } },
+ "traffic": [{ "key": "all", "segments": "*", "percentage": 100000 }]
+ },
+ "disabledCheckout": {
+ "bucketBy": ["userId"],
+ "disabledVariationValue": "control",
+ "traffic": []
+ },
+ "disabledNoVariation": {
+ "bucketBy": ["userId"],
+ "traffic": []
+ },
+ "experiment": {
+ "bucketBy": ["userId"],
+ "variations": [{ "value": "treatment" }],
+ "traffic": [
+ {
+ "key": "all",
+ "segments": "*",
+ "percentage": 100000,
+ "allocation": [{ "variation": "treatment", "range": [0, 100000] }]
+ }
+ ]
+ },
+ "checkout": {
+ "bucketBy": ["userId"],
+ "variablesSchema": {
+ "limit": {
+ "type": "integer",
+ "defaultValue": 5
+ },
+ "label": {
+ "type": "string",
+ "defaultValue": "standard"
+ }
+ },
+ "traffic": [
+ {
+ "key": "everyone",
+ "segments": "*",
+ "percentage": 100000
+ }
+ ]
+ }
+ },
+ "variables": {
+ "membershipFixture": {
+ "type": "object",
+ "defaultValue": {
+ "roles": ["member", "owner"],
+ "empty": [],
+ "booleans": [true, false],
+ "nulls": [null],
+ "structured": [{ "role": "owner" }],
+ "scalar": "owner",
+ "candidates": ["absent", "owner"],
+ "badCandidates": [{}, "owner"]
+ }
+ },
+ "unicodeTimestamps": {
+ "type": "object",
+ "defaultValue": {
+ "arabic": "٢٠٢٦-٠١-٠١T00:00:00Z",
+ "fullwidth": "2026-01-01T00:00:00Z",
+ "fraction": "2026-01-01T00:00:00.١٢٣Z",
+ "offset": "2026-01-01T00:00:00+٠١:00"
+ }
+ },
+ "emptiness": {
+ "type": "object",
+ "defaultValue": {
+ "empty string": "",
+ "whitespace": " \t",
+ "text": "hello",
+ "empty array": [],
+ "array containing null": [null],
+ "array containing empty array": [[]],
+ "empty object": {},
+ "object containing null": {
+ "value": null
+ },
+ "object containing empty object": {
+ "nested": {}
+ },
+ "null": null,
+ "zero": 0,
+ "false": false
+ }
+ },
+ "settings": {
+ "type": "object",
+ "defaultValue": {
+ "min": 2,
+ "max": 10,
+ "regions": ["nl", "gb"],
+ "nested": {
+ "nullable": null,
+ "dot.key": 7
+ },
+ "items": [
+ {
+ "limit": 4
+ }
+ ],
+ "active": true
+ }
+ },
+ "limit": {
+ "type": "integer",
+ "defaultValue": 12
+ },
+ "label": {
+ "type": "string",
+ "defaultValue": "Hello world"
+ },
+ "version": {
+ "type": "string",
+ "defaultValue": "2.3.1"
+ },
+ "timestamp": {
+ "type": "string",
+ "defaultValue": "2026-09-13T12:00:00Z"
+ },
+ "leapTimestamp": {
+ "type": "string",
+ "defaultValue": "2024-02-29T12:34:56Z"
+ },
+ "equivalentTimestamp": {
+ "type": "string",
+ "defaultValue": "2026-09-13T14:00:00+02:00"
+ },
+ "millisecondTimestamp": {
+ "type": "string",
+ "defaultValue": "2026-09-13T12:00:00.123Z"
+ },
+ "invalidCalendarTimestamp": {
+ "type": "string",
+ "defaultValue": "2026-02-30T12:00:00Z"
+ },
+ "invalidFractionTimestamp": {
+ "type": "string",
+ "defaultValue": "2026-09-13T12:00:00.1234567890Z"
+ },
+ "preciseTimestamp": {
+ "type": "string",
+ "defaultValue": "2026-09-13T12:00:00.123999999Z"
+ },
+ "invalidOffsetTimestamp": {
+ "type": "string",
+ "defaultValue": "2026-09-13T12:00:00+14:01"
+ },
+ "nil": {
+ "type": "object",
+ "defaultValue": null
+ },
+ "regions": {
+ "type": "array",
+ "defaultValue": ["nl", "gb"]
+ },
+ "pattern": {
+ "type": "string",
+ "defaultValue": "^Hello"
+ },
+ "badPattern": {
+ "type": "string",
+ "defaultValue": "["
+ }
+ }
+ },
+ "cases": [
+ {
+ "name": "includesAny: hit",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "includesAny: miss",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": ["admin"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "includesAny: empty actual",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "empty",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "includesAny: empty candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": []
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "includesAny: boolean is not zero",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "booleans",
+ "operator": "includesAny",
+ "value": [0]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "includesAny: null element",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "nulls",
+ "operator": "includesAny",
+ "value": [null]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "includesAny: object element ignored",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "structured",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "includesAny: malformed candidate",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": [{}]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: malformed after a match",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": ["owner", {}]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: nonarray candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": "owner"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: nonarray actual",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "scalar",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: null actual is a check error",
+ "checks": [
+ {
+ "variable": "nil",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: missing path remains false",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "missing",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "includesAny: resolved array candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "compareTo": {
+ "variable": "membershipFixture",
+ "path": "candidates"
+ }
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "includesAny: resolved invalid candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "compareTo": {
+ "variable": "membershipFixture",
+ "path": "badCandidates"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: feature variable",
+ "checks": [
+ {
+ "feature": "membershipFeature",
+ "variable": "roles",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "includesAny: comparison errors are not inverted",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": [{}]
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "includesAny: short circuit skips a bad operand",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "includesAny",
+ "value": ["owner"]
+ },
+ {
+ "variable": "membershipFixture",
+ "path": "scalar",
+ "operator": "includesAny",
+ "value": ["owner"]
+ }
+ ]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: hit",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: miss",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": ["admin"]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: empty actual",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "empty",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: empty candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": []
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: boolean is not zero",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "booleans",
+ "operator": "notIncludesAny",
+ "value": [0]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: null element",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "nulls",
+ "operator": "notIncludesAny",
+ "value": [null]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: object element ignored",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "structured",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: malformed candidate",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": [{}]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: malformed after a match",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": ["owner", {}]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: nonarray candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": "owner"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: nonarray actual",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "scalar",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: null actual is a check error",
+ "checks": [
+ {
+ "variable": "nil",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: missing path remains false",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "missing",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: resolved array candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "compareTo": {
+ "variable": "membershipFixture",
+ "path": "candidates"
+ }
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: resolved invalid candidates",
+ "checks": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "compareTo": {
+ "variable": "membershipFixture",
+ "path": "badCandidates"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: feature variable",
+ "checks": [
+ {
+ "feature": "membershipFeature",
+ "variable": "roles",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ],
+ "passed": false,
+ "error": false
+ },
+ {
+ "name": "notIncludesAny: comparison errors are not inverted",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": [{}]
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notIncludesAny: short circuit skips a bad operand",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "membershipFixture",
+ "path": "roles",
+ "operator": "notIncludesAny",
+ "value": ["admin"]
+ },
+ {
+ "variable": "membershipFixture",
+ "path": "scalar",
+ "operator": "notIncludesAny",
+ "value": ["owner"]
+ }
+ ]
+ }
+ ],
+ "passed": true,
+ "error": false
+ },
+ {
+ "name": "resolved Unicode timestamp arabic is rejected",
+ "checks": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "arabic",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "resolved Unicode timestamp fullwidth is rejected",
+ "checks": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "fullwidth",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "resolved Unicode timestamp fraction is rejected",
+ "checks": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "fraction",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "resolved Unicode timestamp offset is rejected",
+ "checks": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "offset",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "resolved Unicode timestamp comparison operand is rejected",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "compareTo": {
+ "variable": "unicodeTimestamps",
+ "path": "arabic"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "Unicode timestamp error propagates through not",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "arabic",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "Unicode timestamp error propagates through and",
+ "checks": [
+ {
+ "and": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "arabic",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "Unicode timestamp error propagates through or",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "unicodeTimestamps",
+ "path": "arabic",
+ "operator": "before",
+ "value": "2027-01-01T00:00:00Z"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects empty regex flags",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "regexFlags": ""
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects null regex flags",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "regexFlags": null
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects null value",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "value": null
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects false value",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "value": false
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects null comparison",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "compareTo": null
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects empty regex flags",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "regexFlags": ""
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects null regex flags",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "regexFlags": null
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects null value",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "value": null
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects false value",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "value": false
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects null comparison",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "compareTo": null
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty: empty string",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["empty string"],
+ "operator": "empty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notEmpty: empty string",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["empty string"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "empty: whitespace",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["whitespace"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: whitespace",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["whitespace"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "empty: text",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["text"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: text",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["text"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "empty: empty array",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["empty array"],
+ "operator": "empty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notEmpty: empty array",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["empty array"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "empty: array containing null",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["array containing null"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: array containing null",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["array containing null"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "empty: array containing empty array",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["array containing empty array"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: array containing empty array",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["array containing empty array"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "empty: empty object",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["empty object"],
+ "operator": "empty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notEmpty: empty object",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["empty object"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "empty: object containing null",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["object containing null"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: object containing null",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["object containing null"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "empty: object containing empty object",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["object containing empty object"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: object containing empty object",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["object containing empty object"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "empty: null",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["null"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: null",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["null"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "empty: zero",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["zero"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty: zero",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["zero"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty: false",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["false"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty: false",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["false"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty: missing",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["missing"],
+ "operator": "empty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEmpty: missing",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "path": ["missing"],
+ "operator": "notEmpty"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "empty rejects value",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "value": ""
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects compareTo",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "compareTo": {
+ "variable": "emptiness"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty rejects regexFlags",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "regexFlags": "i"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty validates skipped operand",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "emptiness",
+ "operator": "exists"
+ },
+ {
+ "variable": "emptiness",
+ "operator": "empty",
+ "value": []
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty error cannot be inverted",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "emptiness",
+ "path": ["zero"],
+ "operator": "empty"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects value",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "value": ""
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects compareTo",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "compareTo": {
+ "variable": "emptiness"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty rejects regexFlags",
+ "checks": [
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "regexFlags": "i"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty validates skipped operand",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "emptiness",
+ "operator": "exists"
+ },
+ {
+ "variable": "emptiness",
+ "operator": "notEmpty",
+ "value": []
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "notEmpty error cannot be inverted",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "emptiness",
+ "path": ["zero"],
+ "operator": "notEmpty"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature flag enabled",
+ "checks": [{ "feature": "checkout", "enabled": true }],
+ "passed": true
+ },
+ {
+ "name": "feature flag disabled",
+ "checks": [{ "feature": "disabledCheckout", "enabled": false }],
+ "passed": true
+ },
+ {
+ "name": "flag mismatch is ordinary failure",
+ "checks": [{ "feature": "checkout", "enabled": false }],
+ "passed": false
+ },
+ {
+ "name": "variation only enabled",
+ "checks": [{ "feature": "experiment", "variation": "treatment" }],
+ "passed": true
+ },
+ {
+ "name": "variation only does not imply enabled",
+ "checks": [{ "feature": "disabledCheckout", "variation": "control" }],
+ "passed": true
+ },
+ {
+ "name": "enabled and variation both match",
+ "checks": [{ "feature": "experiment", "enabled": true, "variation": "treatment" }],
+ "passed": true
+ },
+ {
+ "name": "disabled and disabled variation both match",
+ "checks": [{ "feature": "disabledCheckout", "enabled": false, "variation": "control" }],
+ "passed": true
+ },
+ {
+ "name": "enabled mismatch is not rescued by matching variation",
+ "checks": [{ "feature": "disabledCheckout", "enabled": true, "variation": "control" }],
+ "passed": false
+ },
+ {
+ "name": "variation mismatch is not rescued by matching enabled",
+ "checks": [{ "feature": "experiment", "enabled": true, "variation": "control" }],
+ "passed": false
+ },
+ {
+ "name": "null variation on enabled feature",
+ "checks": [{ "feature": "checkout", "variation": null }],
+ "passed": true
+ },
+ {
+ "name": "null variation on disabled feature",
+ "checks": [{ "feature": "disabledNoVariation", "enabled": false, "variation": null }],
+ "passed": true
+ },
+ {
+ "name": "null variation mismatch",
+ "checks": [{ "feature": "experiment", "variation": null }],
+ "passed": false
+ },
+ {
+ "name": "empty variation string is valid expectation",
+ "checks": [{ "feature": "experiment", "variation": "" }],
+ "passed": false
+ },
+ {
+ "name": "missing feature cannot satisfy disabled",
+ "checks": [{ "feature": "absent", "enabled": false }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "missing feature cannot satisfy null variation",
+ "checks": [{ "feature": "absent", "variation": null }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "not cannot hide missing feature",
+ "checks": [{ "not": [{ "feature": "absent", "enabled": true }] }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "or cannot hide reached missing feature",
+ "checks": [
+ {
+ "or": [
+ { "feature": "absent", "enabled": true },
+ { "feature": "checkout", "enabled": true }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "or skips unreached missing feature",
+ "checks": [
+ {
+ "or": [
+ { "feature": "checkout", "enabled": true },
+ { "feature": "absent", "enabled": true }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "and skips unreached missing feature",
+ "checks": [
+ {
+ "and": [
+ { "feature": "checkout", "enabled": false },
+ { "feature": "absent", "enabled": true }
+ ]
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "not negates feature implicit and",
+ "checks": [
+ {
+ "not": [
+ { "feature": "checkout", "enabled": true },
+ { "feature": "disabledCheckout", "enabled": true }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "mixed feature and variable checks",
+ "checks": [
+ {
+ "and": [
+ { "feature": "checkout", "enabled": true },
+ { "feature": "checkout", "variable": "limit", "operator": "equals", "value": 5 },
+ { "variable": "limit", "operator": "equals", "value": 12 }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "owner does not turn flag into feature variable",
+ "owner": "settings",
+ "checks": [
+ { "feature": "checkout", "enabled": true },
+ { "path": "min", "operator": "equals", "value": 2 }
+ ],
+ "passed": true
+ },
+ {
+ "name": "bare feature check is invalid",
+ "checks": [{ "feature": "checkout" }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check requires feature key",
+ "owner": "settings",
+ "checks": [{ "enabled": true }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects empty feature key",
+ "checks": [{ "feature": "", "enabled": true }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects numeric feature key",
+ "checks": [{ "feature": 7, "enabled": true }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects string enabled",
+ "checks": [{ "feature": "checkout", "enabled": "true" }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects null enabled",
+ "checks": [{ "feature": "checkout", "enabled": null }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects numeric variation",
+ "checks": [{ "feature": "checkout", "variation": 1 }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects boolean variation",
+ "checks": [{ "feature": "checkout", "variation": false }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects array variation",
+ "checks": [{ "feature": "checkout", "variation": ["control"] }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects variable even with owner",
+ "owner": "settings",
+ "checks": [{ "feature": "checkout", "variable": "limit", "enabled": true }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects operator",
+ "checks": [{ "feature": "checkout", "enabled": true, "operator": "equals", "value": true }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects path",
+ "checks": [{ "feature": "checkout", "variation": null, "path": [] }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects compareTo",
+ "checks": [{ "feature": "checkout", "enabled": true, "compareTo": { "variable": "limit" } }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature check rejects unknown field",
+ "checks": [{ "feature": "checkout", "enabled": true, "extra": true }],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "invalid skipped feature leaf still errors",
+ "checks": [
+ {
+ "or": [
+ { "feature": "checkout", "enabled": true },
+ { "feature": "checkout", "enabled": "true" }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "compareTo cannot resolve a flag",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "compareTo": { "feature": "checkout", "enabled": true }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "mixed feature and group properties are invalid",
+ "checks": [
+ {
+ "feature": "checkout",
+ "enabled": true,
+ "and": [{ "feature": "checkout", "enabled": true }]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "explicit null regex flags",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "Hello",
+ "regexFlags": null
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "structured membership literal",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": "in",
+ "value": [{}]
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "null path component",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "settings",
+ "path": ["items", null],
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "unsafe integer string path",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "settings",
+ "path": "items.9007199254740992",
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "nonstring operator",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": true,
+ "value": 12
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "noncapturing regex is outside portable subset",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "(?:Hello)"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "regex backreference is outside portable subset",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "(Hello)\\1"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "possessive regex is outside portable subset",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "Hello++"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "inline regex flags are outside portable subset",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "(?i)Hello"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "large safe array index is missing rather than invalid",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": ["items", 4294967296],
+ "operator": "notExists"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "explicit null variable",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": null,
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "explicit null feature",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "feature": null,
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "explicit null path",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "path": null,
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "numeric variable name",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": 12,
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty feature name",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "feature": "",
+ "variable": "limit",
+ "operator": "exists"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "numeric compareTo name",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "compareTo": {
+ "variable": 12
+ }
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "invalid numeric literal in skipped OR",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "value": "12"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "bad regex in skipped OR",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "["
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "invalid flags in skipped OR",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "Hello",
+ "regexFlags": false
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "duplicate flags in skipped OR",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "Hello",
+ "regexFlags": "ii"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "structured literal in skipped OR",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": {}
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "malformed membership literal in skipped OR",
+ "owner": "settings",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": "in",
+ "value": "12"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "boolean in is invalid",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "active",
+ "operator": "in",
+ "value": [true]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "objects in array do not match primitive needle",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "items",
+ "operator": "notIncludes",
+ "value": 4
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "mixed primitive types use strict equality",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "active",
+ "operator": "notEquals",
+ "value": 1
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "literal integer equality",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "failed literal comparison",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 10
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "notEquals",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "notEquals",
+ "value": 13
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "greaterThan",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "value": 11
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "greaterThanOrEquals",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "greaterThanOrEquals",
+ "value": 12
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "lessThan",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 13
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "lessThanOrEquals",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "lessThanOrEquals",
+ "value": 12
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "in",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "in",
+ "value": [10, 12]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notIn",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "notIn",
+ "value": [9]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "global reference",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "compareTo": {
+ "variable": "settings",
+ "path": "max"
+ }
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "feature reference",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "compareTo": {
+ "feature": "checkout",
+ "variable": "limit"
+ }
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "feature left operand",
+ "checks": [
+ {
+ "feature": "checkout",
+ "variable": "limit",
+ "operator": "equals",
+ "value": 5
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "owner reference",
+ "owner": "settings",
+ "checks": [
+ {
+ "path": "min",
+ "operator": "lessThan",
+ "compareTo": {
+ "path": "max"
+ }
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "compareTo inherits owner not left operand",
+ "owner": "settings",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "compareTo": {
+ "path": "max"
+ }
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "dot numeric index",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "items.0.limit",
+ "operator": "equals",
+ "value": 4
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "array path numeric index",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": ["items", 0, "limit"],
+ "operator": "equals",
+ "value": 4
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "literal dot key",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": ["nested", "dot.key"],
+ "operator": "equals",
+ "value": 7
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "array string index matches requiredVariables",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": ["items", "0", "limit"],
+ "operator": "equals",
+ "value": 4
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "array length matches requiredVariables",
+ "checks": [
+ { "variable": "settings", "path": ["items", "length"], "operator": "equals", "value": 1 }
+ ],
+ "passed": true
+ },
+ {
+ "name": "padded array string index stays missing",
+ "checks": [{ "variable": "settings", "path": ["items", "00"], "operator": "notExists" }],
+ "passed": true
+ },
+ {
+ "name": "empty array path is whole value",
+ "checks": [
+ {
+ "variable": "limit",
+ "path": [],
+ "operator": "equals",
+ "value": 12
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "null path",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "nested.nullable",
+ "operator": "isNull"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "null getter",
+ "checks": [
+ {
+ "variable": "nil",
+ "operator": "isNull"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "null exists",
+ "checks": [
+ {
+ "variable": "nil",
+ "operator": "exists"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "missing nested path",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "noSuch",
+ "operator": "notExists"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "missing is not null",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "noSuch",
+ "operator": "isNull"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "missing notNull is false",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "noSuch",
+ "operator": "notNull"
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "missing notEquals is false",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "noSuch",
+ "operator": "notEquals",
+ "value": 3
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "missing right operand false",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "notEquals",
+ "compareTo": {
+ "variable": "settings",
+ "path": "noSuch"
+ }
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "boolean literal",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "active",
+ "operator": "equals",
+ "value": true
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "contains",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "contains",
+ "value": "world"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notContains",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "notContains",
+ "value": "absent"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "startsWith",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "startsWith",
+ "value": "Hello"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "endsWith",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "endsWith",
+ "value": "world"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "matches",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "^Hello"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notMatches",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "notMatches",
+ "value": "^Goodbye"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "regex flags",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "^hello",
+ "regexFlags": "i"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "regex from variable",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "compareTo": {
+ "variable": "pattern"
+ }
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "semverEquals",
+ "checks": [
+ {
+ "variable": "version",
+ "operator": "semverEquals",
+ "value": "2.3.1"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "semverNotEquals",
+ "checks": [
+ {
+ "variable": "version",
+ "operator": "semverNotEquals",
+ "value": "2.3.2"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "semverGreaterThan",
+ "checks": [
+ {
+ "variable": "version",
+ "operator": "semverGreaterThan",
+ "value": "2.3.0"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "semverGreaterThanOrEquals",
+ "checks": [
+ {
+ "variable": "version",
+ "operator": "semverGreaterThanOrEquals",
+ "value": "2.3.1"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "semverLessThan",
+ "checks": [
+ {
+ "variable": "version",
+ "operator": "semverLessThan",
+ "value": "3.0.0"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "semverLessThanOrEquals",
+ "checks": [
+ {
+ "variable": "version",
+ "operator": "semverLessThanOrEquals",
+ "value": "2.3.1"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "before",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "2026-09-14T00:00:00Z"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "after",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "after",
+ "value": "2026-09-12T00:00:00Z"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "includes",
+ "checks": [
+ {
+ "variable": "regions",
+ "operator": "includes",
+ "value": "nl"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "notIncludes",
+ "checks": [
+ {
+ "variable": "regions",
+ "operator": "notIncludes",
+ "value": "fr"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "array reference membership",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": ["regions", 0],
+ "operator": "in",
+ "compareTo": {
+ "variable": "regions"
+ }
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "and group",
+ "checks": [
+ {
+ "and": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "value": 5
+ },
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 20
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "or group",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 5
+ },
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "not negates AND",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "value": 5
+ },
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 10
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "not all true fails",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "value": 5
+ },
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 20
+ }
+ ]
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "not of or none match",
+ "checks": [
+ {
+ "not": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 5
+ },
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "value": 20
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "outer array AND",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "limit",
+ "operator": "lessThan",
+ "value": 10
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "short circuit skips unresolved ref",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "absent",
+ "operator": "isNull"
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "AND short circuit",
+ "checks": [
+ {
+ "and": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 13
+ },
+ {
+ "variable": "absent",
+ "operator": "isNull"
+ }
+ ]
+ }
+ ],
+ "passed": false
+ },
+ {
+ "name": "missing global is error",
+ "checks": [
+ {
+ "variable": "absent",
+ "operator": "isNull"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "missing feature variable is error",
+ "checks": [
+ {
+ "feature": "checkout",
+ "variable": "absent",
+ "operator": "isNull"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "unresolved ref cannot invert",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "absent",
+ "operator": "isNull"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "numeric type error cannot invert",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "compareTo": {
+ "variable": "label"
+ }
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "OR cannot hide error",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "greaterThan",
+ "compareTo": {
+ "variable": "label"
+ }
+ },
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "null numeric error",
+ "checks": [
+ {
+ "variable": "nil",
+ "operator": "greaterThan",
+ "value": 1
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "structured equality error",
+ "checks": [
+ {
+ "variable": "settings",
+ "operator": "equals",
+ "compareTo": {
+ "variable": "settings"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "regex reference error",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "compareTo": {
+ "variable": "badPattern"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty checks",
+ "checks": [],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "empty group",
+ "checks": [
+ {
+ "not": []
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "mixed group",
+ "checks": [
+ {
+ "and": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ }
+ ],
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "scenario needs variable",
+ "checks": [
+ {
+ "operator": "equals",
+ "value": 12
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "feature reference needs variable",
+ "checks": [
+ {
+ "feature": "checkout",
+ "operator": "equals",
+ "value": 12
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "unsafe path",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "__proto__.x",
+ "operator": "exists"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "wildcard path",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": "items.*.limit",
+ "operator": "exists"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "negative path",
+ "checks": [
+ {
+ "variable": "settings",
+ "path": ["items", -1],
+ "operator": "exists"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "both operands",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12,
+ "compareTo": {
+ "variable": "limit"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "neither operand",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "unary operand",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "exists",
+ "value": 12
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "invalid operator",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "futureOperator",
+ "value": 12
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "unknown property",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12,
+ "wat": true
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "bad literal regex",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "["
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "lookahead not portable",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "(?=Hello)"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "invalid flags",
+ "checks": [
+ {
+ "variable": "label",
+ "operator": "matches",
+ "value": "Hello",
+ "regexFlags": "u"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "flags nonregex",
+ "checks": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12,
+ "regexFlags": "i"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "date check truncates 4 digits",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "before",
+ "value": "2026-09-13T12:00:00.1234Z"
+ }
+ ]
+ },
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "after",
+ "value": "2026-09-13T12:00:00.1234Z"
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "date check truncates 6 digits",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "before",
+ "value": "2026-09-13T12:00:00.123456Z"
+ }
+ ]
+ },
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "after",
+ "value": "2026-09-13T12:00:00.123456Z"
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "date check truncates 7 digits",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "before",
+ "value": "2026-09-13T12:00:00.1234567Z"
+ }
+ ]
+ },
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "after",
+ "value": "2026-09-13T12:00:00.1234567Z"
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "date check truncates 9 digits",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "before",
+ "value": "2026-09-13T12:00:00.123456789Z"
+ }
+ ]
+ },
+ {
+ "not": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "after",
+ "value": "2026-09-13T12:00:00.123456789Z"
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "date check truncates resolved nanoseconds",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "preciseTimestamp",
+ "operator": "after",
+ "compareTo": {
+ "variable": "millisecondTimestamp"
+ }
+ }
+ ]
+ },
+ {
+ "variable": "preciseTimestamp",
+ "operator": "before",
+ "value": "2026-09-13T12:00:00.124Z"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "portable leap day",
+ "checks": [
+ {
+ "variable": "leapTimestamp",
+ "operator": "before",
+ "value": "2024-03-01T00:00:00Z"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "timezone equivalent instants",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "compareTo": {
+ "variable": "equivalentTimestamp"
+ }
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "portable milliseconds",
+ "checks": [
+ {
+ "variable": "millisecondTimestamp",
+ "operator": "after",
+ "value": "2026-09-13T12:00:00.122Z"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "minimum portable UTC instant",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "0001-01-01T00:00:00Z"
+ }
+ ]
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "maximum portable UTC instant",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "9999-12-31T23:59:59.999Z"
+ }
+ ],
+ "passed": true
+ },
+ {
+ "name": "invalid calendar literal in skipped OR",
+ "checks": [
+ {
+ "or": [
+ {
+ "variable": "limit",
+ "operator": "equals",
+ "value": 12
+ },
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "2026-02-30T12:00:00Z"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "invalid resolved calendar cannot invert",
+ "checks": [
+ {
+ "not": [
+ {
+ "variable": "invalidCalendarTimestamp",
+ "operator": "before",
+ "value": "2026-03-01T00:00:00Z"
+ }
+ ]
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "24 hour timestamp is invalid",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "2026-09-13T24:00:00Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "leap second timestamp is invalid",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "2026-09-13T23:59:60Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "year zero timestamp is invalid",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "after",
+ "value": "0000-12-31T23:59:59Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "resolved excessive fractional precision is invalid",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "compareTo": {
+ "variable": "invalidFractionTimestamp"
+ }
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "resolved excessive offset is invalid",
+ "checks": [
+ {
+ "variable": "invalidOffsetTimestamp",
+ "operator": "before",
+ "value": "2026-09-14T00:00:00Z"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "UTC instant below year one is invalid",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "after",
+ "value": "0001-01-01T00:00:00+00:01"
+ }
+ ],
+ "passed": false,
+ "error": true
+ },
+ {
+ "name": "UTC instant above year 9999 is invalid",
+ "checks": [
+ {
+ "variable": "timestamp",
+ "operator": "before",
+ "value": "9999-12-31T23:59:59.999-00:01"
+ }
+ ],
+ "passed": false,
+ "error": true
+ }
+ ]
+}
diff --git a/featurevisor-openfeature/src/main/java/com/featurevisor/openfeature/FeaturevisorOpenFeatureProvider.java b/featurevisor-openfeature/src/main/java/com/featurevisor/openfeature/FeaturevisorOpenFeatureProvider.java
index 23d4480..f8e6b1d 100644
--- a/featurevisor-openfeature/src/main/java/com/featurevisor/openfeature/FeaturevisorOpenFeatureProvider.java
+++ b/featurevisor-openfeature/src/main/java/com/featurevisor/openfeature/FeaturevisorOpenFeatureProvider.java
@@ -1,15 +1,19 @@
package com.featurevisor.openfeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.featurevisor.sdk.Evaluation;
import com.featurevisor.sdk.Featurevisor;
-import com.featurevisor.sdk.FeaturevisorDiagnosticHandler;
+import com.featurevisor.sdk.FeaturevisorDiagnostic;
import com.featurevisor.sdk.FeaturevisorEventName;
import com.featurevisor.sdk.FeaturevisorUnsubscribe;
import com.featurevisor.sdk.VariableType;
import dev.openfeature.sdk.ErrorCode;
import dev.openfeature.sdk.EvaluationContext;
-import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.EventProvider;
+import dev.openfeature.sdk.ProviderEventDetails;
+import dev.openfeature.sdk.ProviderState;
+import dev.openfeature.sdk.exceptions.ParseError;
import dev.openfeature.sdk.ImmutableMetadata;
import dev.openfeature.sdk.Metadata;
import dev.openfeature.sdk.ProviderEvaluation;
@@ -23,9 +27,10 @@
import java.util.List;
import java.util.Map;
-/** OpenFeature provider backed by the Featurevisor v3 SDK. */
-public final class FeaturevisorOpenFeatureProvider implements FeatureProvider {
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+/** OpenFeature provider backed by the Featurevisor SDK. */
+public final class FeaturevisorOpenFeatureProvider extends EventProvider {
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
@FunctionalInterface
public interface TrackingHandler {
@@ -57,8 +62,9 @@ public static final class Options {
private final String globalVariablePrefix;
private final TrackingHandler onTrack;
private final FeaturevisorUnsubscribe datafileUnsubscribe;
+ private final FeaturevisorUnsubscribe errorUnsubscribe;
private final boolean ownsFeaturevisor;
- private String datafileError;
+ private volatile String datafileError;
public FeaturevisorOpenFeatureProvider(Options options) {
Options resolved = options != null ? options : new Options();
@@ -76,22 +82,42 @@ public FeaturevisorOpenFeatureProvider(Options options) {
} else {
Featurevisor.FeaturevisorOptions fvOptions = resolved.featurevisorOptions != null
? resolved.featurevisorOptions : new Featurevisor.FeaturevisorOptions();
- if (fvOptions.getDatafileString() != null) {
- try {
- com.featurevisor.sdk.DatafileContent.fromJson(fvOptions.getDatafileString());
- } catch (Exception ignored) {
- datafileError = "Could not parse datafile";
+ this.featurevisor = Featurevisor.createFeaturevisor(new Featurevisor.FeaturevisorOptions()
+ .context(fvOptions.getContext()).logLevel(fvOptions.getLogLevel())
+ .stickyFeatures(fvOptions.getStickyFeatures()).stickyVariables(fvOptions.getStickyVariables())
+ .modules(fvOptions.getModules()).onDiagnostic(fvOptions.getOnDiagnostic() != null
+ ? fvOptions.getOnDiagnostic() : diagnostic -> {}));
+ }
+ this.datafileUnsubscribe = this.featurevisor.on(FeaturevisorEventName.DATAFILE_SET, details -> {
+ boolean recovering = datafileError != null;
+ datafileError = null;
+ if (recovering) emitProviderReady(ProviderEventDetails.builder().build());
+ });
+ this.errorUnsubscribe = this.featurevisor.on(FeaturevisorEventName.ERROR, details -> {
+ Object diagnostic = details.get("diagnostic");
+ if (diagnostic instanceof FeaturevisorDiagnostic) {
+ FeaturevisorDiagnostic value = (FeaturevisorDiagnostic) diagnostic;
+ if ("invalid_datafile".equals(value.getCode())) {
+ datafileError = value.getMessage();
+ emitProviderError(ProviderEventDetails.builder().message(datafileError).errorCode(ErrorCode.PARSE_ERROR).build());
}
}
- FeaturevisorDiagnosticHandler original = fvOptions.getOnDiagnostic();
- fvOptions.onDiagnostic(diagnostic -> {
- if ("invalid_datafile".equals(diagnostic.getCode())) datafileError = diagnostic.getMessage();
- if ("datafile_set".equals(diagnostic.getCode())) datafileError = null;
- if (original != null) original.handle(diagnostic);
- });
- this.featurevisor = Featurevisor.createFeaturevisor(fvOptions);
+ });
+ if (ownsFeaturevisor && resolved.featurevisorOptions != null) {
+ if (resolved.featurevisorOptions.getDatafile() != null) {
+ this.featurevisor.setDatafile(resolved.featurevisorOptions.getDatafile(), true);
+ } else if (resolved.featurevisorOptions.getDatafileString() != null) {
+ this.featurevisor.setDatafile(resolved.featurevisorOptions.getDatafileString(), true);
+ }
}
- this.datafileUnsubscribe = this.featurevisor.on(FeaturevisorEventName.DATAFILE_SET, details -> datafileError = null);
+ }
+
+ @Override public void initialize(EvaluationContext context) {
+ if (datafileError != null) throw new ParseError(datafileError);
+ }
+
+ @Override public ProviderState getState() {
+ return datafileError != null ? ProviderState.ERROR : ProviderState.READY;
}
public FeaturevisorOpenFeatureProvider(Featurevisor.FeaturevisorOptions options) {
@@ -106,6 +132,7 @@ public FeaturevisorOpenFeatureProvider(Featurevisor featurevisor) {
@Override public Metadata getMetadata() { return () -> "Featurevisor"; }
@Override public void shutdown() {
datafileUnsubscribe.unsubscribe();
+ errorUnsubscribe.unsubscribe();
if (ownsFeaturevisor) featurevisor.close();
}
@Override public void track(String name, EvaluationContext context, TrackingEventDetails details) {
@@ -143,7 +170,7 @@ private ProviderEvaluation