From 50c6a9dae9ec06e605342933eb96e04a16cffeee Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 9 Nov 2026 21:17:02 +0000 Subject: [PATCH 1/2] fix: Require C++ server SDK 3.15.0 so that track sends metric values correctly Generated-by: termag --- .github/variables/cpp-sdk-versions.env | 4 +- .github/workflows/ci.yml | 23 ++ README.md | 4 +- contract-tests/Dockerfile | 38 +++ contract-tests/README.md | 27 +++ contract-tests/nginx.conf | 29 +++ contract-tests/service.lua | 315 +++++++++++++++++++++++++ contract-tests/test-suppressions.txt | 287 ++++++++++++++++++++++ examples/hello-debian/Dockerfile | 2 +- examples/hello-haproxy/Dockerfile | 2 +- examples/hello-nginx/Dockerfile | 2 +- 11 files changed, 726 insertions(+), 7 deletions(-) create mode 100644 contract-tests/Dockerfile create mode 100644 contract-tests/README.md create mode 100644 contract-tests/nginx.conf create mode 100644 contract-tests/service.lua create mode 100644 contract-tests/test-suppressions.txt diff --git a/.github/variables/cpp-sdk-versions.env b/.github/variables/cpp-sdk-versions.env index 17c98cb..5b5c56e 100644 --- a/.github/variables/cpp-sdk-versions.env +++ b/.github/variables/cpp-sdk-versions.env @@ -1,2 +1,2 @@ -sdk=3.14.0 -redis_source=2.4.2 +sdk=3.15.0 +redis_source=2.4.3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d39fd5..1c49b63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,3 +111,26 @@ jobs: --env LAUNCHDARKLY_SDK_KEY="$LAUNCHDARKLY_SDK_KEY" \ --env LAUNCHDARKLY_FLAG_KEY="$LAUNCHDARKLY_FLAG_KEY" \ launchdarkly:${{ matrix.name }} + + contract-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Get C++ Versions + id: cpp-versions + uses: ./.github/actions/cpp-versions + - name: Build contract test service image + run: | + docker build \ + --build-arg="CPP_SDK_VERSION=${{ steps.cpp-versions.outputs.sdk }}" \ + -t lua-contract-tests -f ./contract-tests/Dockerfile . + - name: Launch contract test service + run: docker run -d --name lua-contract-tests --network host lua-contract-tests + - uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1 + with: + test_service_port: 8000 + token: ${{ secrets.GITHUB_TOKEN }} + extra_params: -skip-from=./contract-tests/test-suppressions.txt -status-timeout=30 + - name: Show contract test service logs + if: always() + run: docker logs lua-contract-tests diff --git a/README.md b/README.md index 6bf8c64..be199a8 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ If Redis support is desired, then it optionally depends on the C++ server-side S | Dependency | Minimum Version | Notes | |--------------------------------|--------------------------------------------------------------------------------------------------------------|--------------------------------------------| -| C++ Server-Side SDK | [3.9.0](https://github.com/launchdarkly/cpp-sdks/releases/tag/launchdarkly-cpp-server-v3.9.0) | Required dependency. | -| C++ Server-Side SDK with Redis | [2.1.19](https://github.com/launchdarkly/cpp-sdks/releases/tag/launchdarkly-cpp-server-redis-source-v2.1.19) | Optional, if using Redis as a data source. | +| C++ Server-Side SDK | [3.15.0](https://github.com/launchdarkly/cpp-sdks/releases/tag/launchdarkly-cpp-server-v3.15.0) | Required dependency. | +| C++ Server-Side SDK with Redis | [2.4.3](https://github.com/launchdarkly/cpp-sdks/releases/tag/launchdarkly-cpp-server-redis-source-v2.4.3) | Optional, if using Redis as a data source. | 3rd Party Dependencies diff --git a/contract-tests/Dockerfile b/contract-tests/Dockerfile new file mode 100644 index 0000000..34ffd69 --- /dev/null +++ b/contract-tests/Dockerfile @@ -0,0 +1,38 @@ +# Builds the contract test service: the Lua SDK from this checkout, served by OpenResty. +FROM openresty/openresty:1.31.8.1-0-jammy + +ARG CPP_SDK_VERSION=3.15.0 + +RUN apt-get update && apt-get install -y \ + git netbase curl libssl-dev apt-transport-https ca-certificates \ + software-properties-common \ + cmake ninja-build locales-all + +# OpenResty bundles its own OpenSSL, and the SDK uses that OpenSSL at runtime. +# The bundled OpenSSL looks for CA certificates in a directory that the image does not have. +# This variable points it at the system CA bundle. nginx.conf passes it to the worker process. +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt + +RUN add-apt-repository ppa:mhier/libboost-latest && \ + apt-get update && \ + apt-get install -y boost1.81 + +RUN mkdir cpp-sdk-libs +RUN git clone --branch launchdarkly-cpp-server-v${CPP_SDK_VERSION} https://github.com/launchdarkly/cpp-sdks.git && \ + cd cpp-sdks && \ + mkdir build-dynamic && \ + cd build-dynamic && \ + cmake -GNinja \ + -DLD_BUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF \ + -DLD_BUILD_SHARED_LIBS=ON \ + -DLD_DYNAMIC_LINK_OPENSSL=ON .. && \ + cmake --build . --target launchdarkly-cpp-server && \ + cmake --install . --prefix=../../cpp-sdk-libs + +COPY . . +COPY ./contract-tests/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf +COPY ./contract-tests/service.lua /usr/local/openresty/nginx/contract-tests/ + +RUN luarocks make launchdarkly-server-sdk-[0-9]*-0.rockspec LD_DIR=./cpp-sdk-libs && \ + cp launchdarkly_server_sdk.so /usr/local/openresty/lualib/ diff --git a/contract-tests/README.md b/contract-tests/README.md new file mode 100644 index 0000000..6ae5eb4 --- /dev/null +++ b/contract-tests/README.md @@ -0,0 +1,27 @@ +# Lua SDK contract test service + +This directory contains the test service for the LaunchDarkly [SDK test harness](https://github.com/launchdarkly/sdk-test-harness). The service is an OpenResty application that runs the Lua SDK built from this checkout. It implements the harness's [service specification](https://github.com/launchdarkly/sdk-test-harness/blob/main/docs/service_spec.md). + +## Running locally + +From the **root** of the repository, build and start the service. It listens on port 8000: + +```bash +docker build -t lua-contract-tests -f ./contract-tests/Dockerfile . +docker run --rm --name lua-contract-tests --network host lua-contract-tests +``` + +Then run the test harness against it. The harness calls back to the SDK on port 8111, so the service uses host networking: + +```bash +./sdk-test-harness -url http://localhost:8000 -skip-from ./contract-tests/test-suppressions.txt +``` + +`test-suppressions.txt` lists the tests that are known to fail. Remove a line from it when the SDK starts to pass that test. + +## Known failures + +- Evaluation reasons do not include `ruleIndex`, `ruleId` or `prerequisiteKey`. The C API of the C++ SDK does not expose these fields. +- `allFlags` returns only flag values. The harness expects the full flags state, with `$flagsState` and `$valid`. +- Lua tables cannot tell an empty JSON object from an empty JSON array, so `{}` becomes `[]` in contexts, event data and default values. +- JSON null values inside objects are lost, because the SDK converts them to `nil`. diff --git a/contract-tests/nginx.conf b/contract-tests/nginx.conf new file mode 100644 index 0000000..5d21239 --- /dev/null +++ b/contract-tests/nginx.conf @@ -0,0 +1,29 @@ +# The test service keeps its SDK clients in Lua memory, so it must run in one worker. +worker_processes 1; +error_log /dev/stderr info; + +events { + worker_connections 1024; +} + +env SSL_CERT_FILE; + +http { + access_log off; + + lua_package_path "/usr/local/openresty/nginx/contract-tests/?.lua;;"; + + # Keep request bodies in memory so that the service can read them with ngx.req.get_body_data. + client_max_body_size 10m; + client_body_buffer_size 10m; + + server { + listen 8000; + + location / { + content_by_lua_block { + require("service").handle() + } + } + } +} diff --git a/contract-tests/service.lua b/contract-tests/service.lua new file mode 100644 index 0000000..1fedb8a --- /dev/null +++ b/contract-tests/service.lua @@ -0,0 +1,315 @@ +-- Contract test service for the Lua server-side SDK. +-- The protocol is in https://github.com/launchdarkly/sdk-test-harness/blob/main/docs/service_spec.md. + +local cjson = require("cjson.safe") +local ld = require("launchdarkly_server_sdk") + +cjson.encode_number_precision(16) + +local M = {} + +local DEFAULT_STREAMING_URL = "https://stream.launchdarkly.com" +local DEFAULT_POLLING_URL = "https://sdk.launchdarkly.com" +local DEFAULT_EVENTS_URL = "https://events.launchdarkly.com" + +local CLIENT_PATH = "/clients/" + +local CAPABILITIES = { + "server-side", + "strongly-typed", + "service-endpoints", + "tags", + "server-side-polling", + "inline-context-all", + "anonymous-redaction", + "client-prereq-events", + "instance-id", +} + +local clients = {} +local next_client_id = 1 + +-- The harness treats a JSON null the same as an absent property. +local function opt(value) + if value == cjson.null then + return nil + end + return value +end + +-- A table error stops a request with that status. Any other error gives a 500. +local function fail(status, message) + error({ status = status, message = message }, 0) +end + +local function base_uri(section, endpoints, service, default) + local uri = section and opt(section.baseUri) + if uri == nil or uri == "" then + uri = endpoints and opt(endpoints[service]) + end + if uri == nil or uri == "" then + uri = default + end + return uri +end + +local function make_config(cfg) + local config = {} + + local streaming = opt(cfg.streaming) + local polling = opt(cfg.polling) + local events = opt(cfg.events) + local endpoints = opt(cfg.serviceEndpoints) + + -- The Lua SDK requires all three service endpoints if one is set. + config.serviceEndpoints = { + streamingBaseURL = base_uri(streaming, endpoints, "streaming", DEFAULT_STREAMING_URL), + pollingBaseURL = base_uri(polling, endpoints, "polling", DEFAULT_POLLING_URL), + eventsBaseURL = base_uri(events, endpoints, "events", DEFAULT_EVENTS_URL), + } + + if polling and not streaming then + local polling_config = {} + local interval_ms = opt(polling.pollIntervalMs) + if interval_ms then + polling_config.intervalSeconds = math.ceil(interval_ms / 1000) + end + config.dataSystem = { backgroundSync = { polling = polling_config } } + elseif streaming then + local streaming_config = {} + local delay_ms = opt(streaming.initialRetryDelayMs) + if delay_ms then + streaming_config.initialReconnectDelayMilliseconds = delay_ms + end + config.dataSystem = { backgroundSync = { streaming = streaming_config } } + end + + if events then + config.events = { enabled = true } + local capacity = opt(events.capacity) + if capacity and capacity > 0 then + config.events.capacity = capacity + end + local flush_ms = opt(events.flushIntervalMs) + if flush_ms and flush_ms > 0 then + config.events.flushIntervalMilliseconds = flush_ms + end + if opt(events.allAttributesPrivate) then + config.events.allAttributesPrivate = true + end + local private_attributes = opt(events.globalPrivateAttributes) + if private_attributes then + config.events.privateAttributes = private_attributes + end + else + config.events = { enabled = false } + end + + local tags = opt(cfg.tags) + if tags then + config.appInfo = { + identifier = opt(tags.applicationId), + version = opt(tags.applicationVersion), + } + end + + return config +end + +-- Converts one context kind from the standard context JSON to a makeContext entry. +local function make_kind(json) + local kind = { attributes = {} } + for name, value in pairs(json) do + if name == "key" or name == "name" or name == "anonymous" then + kind[name] = opt(value) + elseif name == "_meta" then + local meta = opt(value) + kind.privateAttributes = meta and opt(meta.privateAttributes) + elseif name ~= "kind" then + kind.attributes[name] = value + end + end + return kind +end + +local function make_context(json) + if type(json) ~= "table" then + fail(400, "context is required") + end + local kinds = {} + if json.kind == "multi" then + for kind, value in pairs(json) do + if kind ~= "kind" then + kinds[kind] = make_kind(value) + end + end + else + kinds[opt(json.kind) or "user"] = make_kind(json) + end + return ld.makeContext(kinds) +end + +-- Closes a client. The SDK closes a client when Lua collects it. +local function close_client(id) + clients[id] = nil + collectgarbage("collect") +end + +local function create_client(body) + local cfg = opt(body.configuration) + if type(cfg) ~= "table" or type(opt(cfg.credential)) ~= "string" then + fail(400, "configuration.credential is required") + end + ngx.log(ngx.INFO, "creating client for test: ", tostring(opt(body.tag))) + + local timeout = opt(cfg.startWaitTimeMs) + if timeout == nil or timeout == 0 then + timeout = 5000 + end + + local id = next_client_id + next_client_id = next_client_id + 1 + clients[id] = ld.clientInit(cfg.credential, timeout, make_config(cfg)) + + if not clients[id]:isInitialized() and not opt(cfg.initCanFail) then + close_client(id) + fail(500, "client initialization failed") + end + + ngx.header["Location"] = CLIENT_PATH .. id + return 201 +end + +local VARIATIONS = { + bool = { "boolVariation", "boolVariationDetail" }, + int = { "intVariation", "intVariationDetail" }, + double = { "doubleVariation", "doubleVariationDetail" }, + string = { "stringVariation", "stringVariationDetail" }, + any = { "jsonVariation", "jsonVariationDetail" }, +} + +local function evaluate(client, params) + local methods = VARIATIONS[params.valueType] + if methods == nil then + fail(400, "unknown valueType: " .. tostring(params.valueType)) + end + local context = make_context(opt(params.context)) + if opt(params.detail) then + local detail = client[methods[2]](client, context, params.flagKey, params.defaultValue) + return { value = detail.value, variationIndex = detail.variationIndex, reason = detail.reason } + end + return { value = client[methods[1]](client, context, params.flagKey, params.defaultValue) } +end + +local function custom_event(client, params) + local context = make_context(opt(params.context)) + local data = opt(params.data) + local metric = opt(params.metricValue) + if metric ~= nil then + client:track(params.eventKey, context, data, metric) + elseif data ~= nil or not opt(params.omitNullData) then + client:track(params.eventKey, context, data) + else + client:track(params.eventKey, context) + end +end + +local COMMANDS = { + evaluate = evaluate, + evaluateAll = function(client, params) + return { state = client:allFlags(make_context(opt(params.context))) } + end, + identifyEvent = function(client, params) + client:identify(make_context(opt(params.context))) + end, + customEvent = custom_event, + flushEvents = function(client) + client:flush() + end, +} + +local function run_command(id, body) + local client = clients[id] + if client == nil then + fail(404, "unknown client: " .. id) + end + local command = COMMANDS[body.command] + if command == nil then + fail(400, "unsupported command: " .. tostring(body.command)) + end + local result = command(client, opt(body[body.command]) or {}) + if result == nil then + return 204 + end + return 200, result +end + +local function read_json_body() + ngx.req.read_body() + local body, err = cjson.decode(ngx.req.get_body_data() or "") + if type(body) ~= "table" then + fail(400, "invalid JSON body: " .. tostring(err)) + end + return body +end + +local function stop_service() + local master_pid = require("ngx.process").get_master_pid() + ngx.timer.at(0, function() + os.execute("kill -QUIT " .. master_pid) + end) + return 204 +end + +local function route(method, uri) + if uri == "/" then + if method == "GET" then + return 200, { name = "lua-server-sdk", clientVersion = ld.version(), capabilities = CAPABILITIES } + elseif method == "POST" then + return create_client(read_json_body()) + elseif method == "DELETE" then + return stop_service() + end + end + + local id = tonumber(uri:match("^" .. CLIENT_PATH .. "(%d+)$")) + if id then + if method == "POST" then + return run_command(id, read_json_body()) + elseif method == "DELETE" then + close_client(id) + return 204 + end + end + + fail(404, "not found: " .. method .. " " .. uri) +end + +function M.handle() + local ok, status, body = pcall(route, ngx.req.get_method(), ngx.var.uri) + if not ok then + local err = status + if type(err) == "table" then + status, body = err.status, err.message + else + status, body = 500, tostring(err) + end + ngx.log(ngx.ERR, "request failed with ", status, ": ", body) + end + + if body == nil then + return ngx.exit(status) + end + + ngx.status = status + if type(body) == "table" then + ngx.header["Content-Type"] = "application/json" + ngx.print(cjson.encode(body)) + else + ngx.header["Content-Type"] = "text/plain" + ngx.print(body) + end + return ngx.exit(ngx.HTTP_OK) +end + +return M diff --git a/contract-tests/test-suppressions.txt b/contract-tests/test-suppressions.txt new file mode 100644 index 0000000..5de6b7e --- /dev/null +++ b/contract-tests/test-suppressions.txt @@ -0,0 +1,287 @@ +evaluation/all flags state/client not ready +evaluation/all flags state/default behavior +evaluation/all flags state/error in flag/with reasons +evaluation/all flags state/error in flag/without reasons +evaluation/all flags state/experimentation +evaluation/all flags state/prerequisites/ignores client-side only for prereq keys +evaluation/all flags state/prerequisites/ignores if not evaluated +evaluation/all flags state/prerequisites/includes top level +evaluation/bucketing/basic bucketing calculations/experiments +evaluation/bucketing/basic bucketing calculations/rollouts +evaluation/bucketing/bucket by non-key attribute/in rollouts/attribute not found +evaluation/bucketing/bucket by non-key attribute/in rollouts/integer value +evaluation/bucketing/bucket by non-key attribute/in rollouts/invalid value type +evaluation/bucketing/bucket by non-key attribute/in rollouts/string value/complex attribute reference +evaluation/bucketing/bucket by non-key attribute/in rollouts/string value/simple attribute name +evaluation/bucketing/bucket by non-key attribute/is ignored in experiments +evaluation/bucketing/selection of context/multi-kind/experiments +evaluation/bucketing/selection of context/multi-kind/rollouts +evaluation/bucketing/selection of context/single-kind/experiments +evaluation/bucketing/selection of context/single-kind/rollouts +evaluation/parameterized/JSON variations/off-flag-any/off-flag-any/evaluate all flags +evaluation/parameterized/JSON variations/off-flag-any/off-flag-any/evaluate flag with detail +evaluation/parameterized/JSON variations/off-flag-any/off-flag-any/evaluate flag without detail +evaluation/parameterized/JSON variations/on-flag-any/on-flag-any/evaluate all flags +evaluation/parameterized/JSON variations/on-flag-any/on-flag-any/evaluate flag with detail +evaluation/parameterized/JSON variations/on-flag-any/on-flag-any/evaluate flag without detail +evaluation/parameterized/attribute references/attribute is interpreted as unescaped name if there is no contextKind/attribute is interpreted as unescaped name if there is no contextKind/evaluate flag with detail +evaluation/parameterized/attribute references/property in nested object/property in nested object/evaluate flag with detail +evaluation/parameterized/attribute references/property in object/property in object/evaluate flag with detail +evaluation/parameterized/attribute references/property that is a numeric string/property that is a numeric string/evaluate flag with detail +evaluation/parameterized/attribute references/property with escape sequence in object/property with escape sequence in object/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute name containing unescaped slash/top-level attribute name containing unescaped slash/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute name containing unescaped tilde/top-level attribute name containing unescaped tilde/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute with no slash prefix/top-level attribute with no slash prefix/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute with slash prefix containing escaped slash/top-level attribute with slash prefix containing escaped slash/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute with slash prefix containing escaped tilde/top-level attribute with slash prefix containing escaped tilde/evaluate flag with detail +evaluation/parameterized/attribute references/top-level attribute with slash prefix/top-level attribute with slash prefix/evaluate flag with detail +evaluation/parameterized/built-in attributes other than kind/anonymous match false/anonymous match false/evaluate flag with detail +evaluation/parameterized/built-in attributes other than kind/anonymous match true/anonymous match true/evaluate flag with detail +evaluation/parameterized/built-in attributes other than kind/key match/key match/evaluate flag with detail +evaluation/parameterized/built-in attributes other than kind/name match/name match/evaluate flag with detail +evaluation/parameterized/clause kind matching/kind match for multi-kind context using matches operator/kind match for multi-kind context using matches operator/evaluate flag with detail +evaluation/parameterized/clause kind matching/kind match for multi-kind context/kind match for multi-kind context/evaluate flag with detail +evaluation/parameterized/clause kind matching/kind match for single-kind context using matches operator/kind match for single-kind context using matches operator/evaluate flag with detail +evaluation/parameterized/clause kind matching/kind match for single-kind context/kind match for single-kind context/evaluate flag with detail +evaluation/parameterized/clause kind matching/multi-kind context with user, clause has default kind, match/multi-kind context with user, clause has default kind, match/evaluate flag with detail +evaluation/parameterized/clause kind matching/multi-kind context, clause specifies existing kind, match/multi-kind context, clause specifies existing kind, match/evaluate flag with detail +evaluation/parameterized/clause kind matching/negated kind match for single-kind context/negated kind match for single-kind context/evaluate flag with detail +evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple clause values/match if expression matches any of multiple clause values/evaluate flag with detail +evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with any of multiple clause values/match if expression matches any of multiple context values with any of multiple clause values/evaluate flag with detail +evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with single clause value/match if expression matches any of multiple context values with single clause value/evaluate flag with detail +evaluation/parameterized/clause negation and value iteration/negated, match if expression does not match single clause value/negated, match if expression does not match single clause value/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/large number equals itself/large number equals itself/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/large number is greater than ordinary number/large number is greater than ordinary number/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/large number is greater than smaller large number/large number is greater than smaller large number/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/number with six integer digits and a fraction compares correctly/number with six integer digits and a fraction compares correctly/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/small negative number is less than zero/small negative number is less than zero/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/small number as context value equals itself/small number as context value equals itself/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/small number as context value is less than small clause value/small number as context value is less than small clause value/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/small number is greater than smaller number/small number is greater than smaller number/evaluate flag with detail +evaluation/parameterized/numbers in exponent notation/small number is less than or equal to itself/small number is less than or equal to itself/evaluate flag with detail +evaluation/parameterized/old user built-in attributes/avatar matches/avatar matches/evaluate flag with detail +evaluation/parameterized/old user built-in attributes/country matches/country matches/evaluate flag with detail +evaluation/parameterized/old user built-in attributes/email matches/email matches/evaluate flag with detail +evaluation/parameterized/old user built-in attributes/firstName matches/firstName matches/evaluate flag with detail +evaluation/parameterized/old user built-in attributes/ip matches/ip matches/evaluate flag with detail +evaluation/parameterized/old user built-in attributes/lastName matches/lastName matches/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/1 in [[1]]/1 in [[1]]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 greaterThanOrEqual ["1"]/5 greaterThanOrEqual ["1"]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 greaterThanOrEqual [{},1]/5 greaterThanOrEqual [{},1]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 lessThan ["100",null,true,100]/5 lessThan ["100",null,true,100]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 lessThan ["100"]/5 lessThan ["100"]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 lessThan [[100]]/5 lessThan [[100]]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 lessThan [null]/5 lessThan [null]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 lessThan [true]/5 lessThan [true]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/5 lessThan [{"a":100}]/5 lessThan [{"a":100}]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/99 bottles startsWith [99]/99 bottles startsWith [99]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/99 matches [99]/99 matches [99]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/a null b contains [null]/a null b contains [null]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/a99b contains [99]/a99b contains [99]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/abc endsWith [{"c":1}]/abc endsWith [{"c":1}]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/abc matches [null,"b"]/abc matches [null,"b"]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/abc startsWith [1,null,"a"]/abc startsWith [1,null,"a"]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/abc startsWith [["a"]]/abc startsWith [["a"]]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/bottles 99 endsWith [99]/bottles 99 endsWith [99]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/null value startsWith [null]/null value startsWith [null]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/true story startsWith [true]/true story startsWith [true]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/x in [null,"x"]/x in [null,"x"]/evaluate flag with detail +evaluation/parameterized/operators - clause values of the wrong type/x in [null]/x in [null]/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543600000 before 1512543601000 passes/1512543600000 before 1512543601000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T00:01:01.000-07:00 passes/1512543600000 before 2017-12-06T00:01:01.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T07:01:01.000Z passes/1512543600000 before 2017-12-06T07:01:01.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543600000 before 2017-12-06T07:01:01Z passes/1512543600000 before 2017-12-06T07:01:01Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543601000 after 1512543600000 passes/1512543601000 after 1512543600000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T00:00:00.000-07:00 passes/1512543601000 after 2017-12-06T00:00:00.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T07:00:00.000Z passes/1512543601000 after 2017-12-06T07:00:00.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/1512543601000 after 2017-12-06T07:00:00Z passes/1512543601000 after 2017-12-06T07:00:00Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 1512543601000 passes/2017-12-06T00:00:00.000-07:00 before 1512543601000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T00:01:01.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01.000Z passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01Z passes/2017-12-06T00:00:00.000-07:00 before 2017-12-06T07:01:01Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 1512543600000 passes/2017-12-06T00:01:01.000-07:00 after 1512543600000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T00:00:00.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00.000Z passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00Z passes/2017-12-06T00:01:01.000-07:00 after 2017-12-06T07:00:00Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 1512543601000 passes/2017-12-06T07:00:00.000Z before 1512543601000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T07:00:00.000Z before 2017-12-06T00:01:01.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01.000Z passes/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01Z passes/2017-12-06T07:00:00.000Z before 2017-12-06T07:01:01Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 1512543601000 passes/2017-12-06T07:00:00Z before 1512543601000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T00:01:01.000-07:00 passes/2017-12-06T07:00:00Z before 2017-12-06T00:01:01.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T07:01:01.000Z passes/2017-12-06T07:00:00Z before 2017-12-06T07:01:01.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:00:00Z before 2017-12-06T07:01:01Z passes/2017-12-06T07:00:00Z before 2017-12-06T07:01:01Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 1512543600000 passes/2017-12-06T07:01:01.000Z after 1512543600000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T07:01:01.000Z after 2017-12-06T00:00:00.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00.000Z passes/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00Z passes/2017-12-06T07:01:01.000Z after 2017-12-06T07:00:00Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 1512543600000 passes/2017-12-06T07:01:01Z after 1512543600000 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T00:00:00.000-07:00 passes/2017-12-06T07:01:01Z after 2017-12-06T00:00:00.000-07:00 passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T07:00:00.000Z passes/2017-12-06T07:01:01Z after 2017-12-06T07:00:00.000Z passes/evaluate flag with detail +evaluation/parameterized/operators - date - unequal/2017-12-06T07:01:01Z after 2017-12-06T07:00:00Z passes/2017-12-06T07:01:01Z after 2017-12-06T07:00:00Z passes/evaluate flag with detail +evaluation/parameterized/operators - equality (any)/user matches clause with single value/user matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (any)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (any)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate flag with detail +evaluation/parameterized/operators - equality (bool)/user matches clause with single value/user matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (bool)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (bool)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate flag with detail +evaluation/parameterized/operators - equality (double)/user matches clause with single value/user matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (double)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (double)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate flag with detail +evaluation/parameterized/operators - equality (int)/user matches clause with single value/user matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (int)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (int)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate flag with detail +evaluation/parameterized/operators - equality (string)/user matches clause with single value/user matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (string)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate flag with detail +evaluation/parameterized/operators - equality (string)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate flag with detail +evaluation/parameterized/operators - equality does not conflate booleans and numbers/boolean false matches boolean clause value false/boolean false matches boolean clause value false/evaluate flag with detail +evaluation/parameterized/operators - equality does not conflate booleans and numbers/boolean true matches boolean clause value true/boolean true matches boolean clause value true/evaluate flag with detail +evaluation/parameterized/operators - equality does not conflate booleans and numbers/integer 0 matches integer clause value 0/integer 0 matches integer clause value 0/evaluate flag with detail +evaluation/parameterized/operators - equality does not conflate booleans and numbers/integer 1 matches integer clause value 1/integer 1 matches integer clause value 1/evaluate flag with detail +evaluation/parameterized/operators - matches/alternation matches a substring/alternation matches a substring/evaluate flag with detail +evaluation/parameterized/operators - matches/both anchors do not match a longer value/both anchors do not match a longer value/evaluate flag with detail +evaluation/parameterized/operators - matches/both anchors match the whole value/both anchors match the whole value/evaluate flag with detail +evaluation/parameterized/operators - matches/character class matches a substring in the middle of the value/character class matches a substring in the middle of the value/evaluate flag with detail +evaluation/parameterized/operators - matches/digit escape matches a substring/digit escape matches a substring/evaluate flag with detail +evaluation/parameterized/operators - matches/end anchor does not match before the end/end anchor does not match before the end/evaluate flag with detail +evaluation/parameterized/operators - matches/end anchor matches at the end/end anchor matches at the end/evaluate flag with detail +evaluation/parameterized/operators - matches/invalid pattern after a non-matching pattern is a non-match/invalid pattern after a non-matching pattern is a non-match/evaluate flag with detail +evaluation/parameterized/operators - matches/invalid pattern does not prevent a later valid pattern from matching/invalid pattern does not prevent a later valid pattern from matching/evaluate flag with detail +evaluation/parameterized/operators - matches/pattern is case-sensitive/pattern is case-sensitive/evaluate flag with detail +evaluation/parameterized/operators - matches/pattern matches a substring in the middle of the value/pattern matches a substring in the middle of the value/evaluate flag with detail +evaluation/parameterized/operators - matches/pattern matches at the end of the value/pattern matches at the end of the value/evaluate flag with detail +evaluation/parameterized/operators - matches/quantifier without operand is a non-match/quantifier without operand is a non-match/evaluate flag with detail +evaluation/parameterized/operators - matches/start anchor does not match after the start/start anchor does not match after the start/evaluate flag with detail +evaluation/parameterized/operators - matches/unclosed character class is a non-match/unclosed character class is a non-match/evaluate flag with detail +evaluation/parameterized/operators - matches/unclosed group is a non-match/unclosed group is a non-match/evaluate flag with detail +evaluation/parameterized/operators - matches/wildcard pattern matches a substring/wildcard pattern matches a substring/evaluate flag with detail +evaluation/parameterized/operators - matches/wildcard pattern requires a character/wildcard pattern requires a character/evaluate flag with detail +evaluation/parameterized/operators - numeric/100 greaterThanOrEqual 100/100 greaterThanOrEqual 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/100 lessThanOrEqual 100/100 lessThanOrEqual 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/100.1 greaterThan 100/100.1 greaterThan 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/100.1 greaterThanOrEqual 100/100.1 greaterThanOrEqual 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/101 greaterThan 100/101 greaterThan 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/101 greaterThanOrEqual 100/101 greaterThanOrEqual 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/99 lessThan 100/99 lessThan 100/evaluate flag with detail +evaluation/parameterized/operators - numeric/99 lessThanOrEqual 100/99 lessThanOrEqual 100/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2 semVerEqual 2.0.0 passes/2 semVerEqual 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2-rc semVerEqual 2.0.0-rc passes/2-rc semVerEqual 2.0.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0 semVerEqual 2.0.0 passes/2.0 semVerEqual 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0-rc semVerEqual 2.0.0-rc passes/2.0-rc semVerEqual 2.0.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2 passes/2.0.0 semVerEqual 2 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0 passes/2.0.0 semVerEqual 2.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0 passes/2.0.0 semVerEqual 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0 semVerEqual 2.0.0+build passes/2.0.0 semVerEqual 2.0.0+build passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0+build semVerEqual 2.0.0 passes/2.0.0+build semVerEqual 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2-rc passes/2.0.0-rc semVerEqual 2-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0-rc passes/2.0.0-rc semVerEqual 2.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc passes/2.0.0-rc semVerEqual 2.0.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0-rc semVerEqual 2.0.0-rc+build passes/2.0.0-rc semVerEqual 2.0.0-rc+build passes/evaluate flag with detail +evaluation/parameterized/operators - semver - equal/2.0.0-rc+build semVerEqual 2.0.0-rc passes/2.0.0-rc+build semVerEqual 2.0.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/1 semVerLessThan 2 passes/1 semVerLessThan 2 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/1.0 semVerLessThan 2.0 passes/1.0 semVerLessThan 2.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/1.0.0 semVerLessThan 2.0.0 passes/1.0.0 semVerLessThan 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2 semVerGreaterThan 1 passes/2 semVerGreaterThan 1 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0 semVerGreaterThan 1.0 passes/2.0 semVerGreaterThan 1.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0 semVerLessThan 2.1 passes/2.0 semVerLessThan 2.1 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 1.0.0 passes/2.0.0 semVerGreaterThan 1.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerGreaterThan 2.0.0-rc passes/2.0.0 semVerGreaterThan 2.0.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.0.1 passes/2.0.0 semVerLessThan 2.0.1 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0 semVerLessThan 2.1.0 passes/2.0.0 semVerLessThan 2.1.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerLessThan 2.0.0 passes/2.0.0-rc semVerLessThan 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc semVerLessThan 2.0.0-rc.1 passes/2.0.0-rc semVerLessThan 2.0.0-rc.1 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.1 semVerGreaterThan 2.0.0-rc passes/2.0.0-rc.1 semVerGreaterThan 2.0.0-rc passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.10.green semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.10.green semVerGreaterThan 2.0.0-rc.2.green passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.10.green passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.10.green passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.green.1 passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.green.1 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.red passes/2.0.0-rc.2.green semVerLessThan 2.0.0-rc.2.red passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green semVerLessThan 2.0.1-rc.2.green passes/2.0.0-rc.2.green semVerLessThan 2.0.1-rc.2.green passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.green.1 semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.2.green.1 semVerGreaterThan 2.0.0-rc.2.green passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.0-rc.2.red semVerGreaterThan 2.0.0-rc.2.green passes/2.0.0-rc.2.red semVerGreaterThan 2.0.0-rc.2.green passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.1 semVerGreaterThan 2.0.0 passes/2.0.1 semVerGreaterThan 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.0.1-rc.2.green semVerGreaterThan 2.0.0-rc.2.green passes/2.0.1-rc.2.green semVerGreaterThan 2.0.0-rc.2.green passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.1 semVerGreaterThan 2.0 passes/2.1 semVerGreaterThan 2.0 passes/evaluate flag with detail +evaluation/parameterized/operators - semver - unequal/2.1.0 semVerGreaterThan 2.0.0 passes/2.1.0 semVerGreaterThan 2.0.0 passes/evaluate flag with detail +evaluation/parameterized/operators - string/[abcsubstringxyz] contains substring/[abcsubstringxyz] contains substring/evaluate flag with detail +evaluation/parameterized/operators - string/[abcsuffix] endsWith suffix/[abcsuffix] endsWith suffix/evaluate flag with detail +evaluation/parameterized/operators - string/[matchThisName] matches ^[A-Za-z]+/[matchThisName] matches ^[A-Za-z]+/evaluate flag with detail +evaluation/parameterized/operators - string/[prefix] startsWith prefix/[prefix] startsWith prefix/evaluate flag with detail +evaluation/parameterized/operators - string/[prefixabc] startsWith prefix/[prefixabc] startsWith prefix/evaluate flag with detail +evaluation/parameterized/operators - string/[substring] contains substring/[substring] contains substring/evaluate flag with detail +evaluation/parameterized/operators - string/[suffix] endsWith suffix/[suffix] endsWith suffix/evaluate flag with detail +evaluation/parameterized/operators - string/abcsubstringxyz contains substring/abcsubstringxyz contains substring/evaluate flag with detail +evaluation/parameterized/operators - string/abcsuffix endsWith suffix/abcsuffix endsWith suffix/evaluate flag with detail +evaluation/parameterized/operators - string/matchThisName matches ^[A-Za-z]+/matchThisName matches ^[A-Za-z]+/evaluate flag with detail +evaluation/parameterized/operators - string/prefix startsWith prefix/prefix startsWith prefix/evaluate flag with detail +evaluation/parameterized/operators - string/prefixabc startsWith prefix/prefixabc startsWith prefix/evaluate flag with detail +evaluation/parameterized/operators - string/substring contains substring/substring contains substring/evaluate flag with detail +evaluation/parameterized/operators - string/suffix endsWith suffix/suffix endsWith suffix/evaluate flag with detail +evaluation/parameterized/prerequisites/failed prerequisite always returns off variation/failed prerequisite always returns off variation/evaluate flag with detail +evaluation/parameterized/prerequisites/first prerequisite is a prerequisite of the second prerequisite/first prerequisite is a prerequisite of the second prerequisite/evaluate flag with detail +evaluation/parameterized/prerequisites/nested prerequisites, all succeed/nested prerequisites, all succeed/evaluate flag with detail +evaluation/parameterized/prerequisites/nested prerequisites, transitive one fails/nested prerequisites, transitive one fails/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite fails regardless of variation if it is off/prerequisite fails regardless of variation if it is off/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite fails/prerequisite fails/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite flag not found/prerequisite flag not found/evaluate flag with detail +evaluation/parameterized/prerequisites/prerequisite succeeds, flag rule matches/prerequisite succeeds, flag rule matches/evaluate flag with detail +evaluation/parameterized/prerequisites/two prerequisites, both succeed, flag rule matches/two prerequisites, both succeed, flag rule matches/evaluate flag with detail +evaluation/parameterized/prerequisites/two prerequisites, both valid, flag rule matches/two prerequisites, both valid, flag rule matches/evaluate flag with detail +evaluation/parameterized/prerequisites/two prerequisites, second fails/two prerequisites, second fails/evaluate flag with detail +evaluation/parameterized/rollout bucket values close to a weight boundary/segment rule bucket value 6.5e-8 below the weight/segment rule bucket value 6.5e-8 below the weight/evaluate flag with detail +evaluation/parameterized/rollout or experiment - last experiment bucket is used if value is past the end/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - last rollout bucket is used if value is past the end/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - user matches first bucket in experiment/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - user matches first bucket in rollout/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - user matches second bucket in experiment/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - user matches second bucket in rollout/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rollout or experiment - user matches untracked bucket in experiment/rule rollout/rule rollout/evaluate flag with detail +evaluation/parameterized/rule match/all clauses of rule1 match/all clauses of rule1 match/evaluate flag with detail +evaluation/parameterized/rule match/negated clause, match becomes non-match/negated clause, match becomes non-match/evaluate flag with detail +evaluation/parameterized/rule match/rule2 matches, after one clause of rule1 fails/rule2 matches, after one clause of rule1 fails/evaluate flag with detail +evaluation/parameterized/rule match/unknown operator is treated as a non-match, can still match another rule/unknown operator is treated as a non-match, can still match another rule/evaluate flag with detail +evaluation/parameterized/segment match - same segment referenced more than once/prerequisite and flag both match the same segment/prerequisite and flag both match the same segment/evaluate flag with detail +evaluation/parameterized/segment match - same segment referenced more than once/second rule matches the segment after the first rule matched it but failed another clause/second rule matches the segment after the first rule matched it but failed another clause/evaluate flag with detail +evaluation/parameterized/segment match - same segment referenced more than once/segment listed twice in one clause/segment listed twice in one clause/evaluate flag with detail +evaluation/parameterized/segment match - same segment referenced more than once/segment matches directly and through another segment/segment matches directly and through another segment/evaluate flag with detail +evaluation/parameterized/segment match - same segment referenced more than once/two clauses in one rule match the same segment/two clauses in one rule match the same segment/evaluate flag with detail +evaluation/parameterized/segment match/clause matches if any of the segments match/clause matches if any of the segments match/evaluate flag with detail +evaluation/parameterized/segment match/excluded list is specific to user kind/excluded list is specific to user kind/evaluate flag with detail +evaluation/parameterized/segment match/excludedContexts non-match for default context kind/excludedContexts non-match for default context kind/evaluate flag with detail +evaluation/parameterized/segment match/excludedContexts non-match for non-default context kind/excludedContexts non-match for non-default context kind/evaluate flag with detail +evaluation/parameterized/segment match/includedContexts match for non-default context kind/includedContexts match for non-default context kind/evaluate flag with detail +evaluation/parameterized/segment match/key does bucket into segment/key does bucket into segment/evaluate flag with detail +evaluation/parameterized/segment match/rule match for non-default context kind/rule match for non-default context kind/evaluate flag with detail +evaluation/parameterized/segment match/user does not match and can be negated/user does not match and can be negated/evaluate flag with detail +evaluation/parameterized/segment match/user matches via include in multi-kind user/user matches via include in multi-kind user/evaluate flag with detail +evaluation/parameterized/segment match/user matches via include/user matches via include/evaluate flag with detail +evaluation/parameterized/segment match/user matches via rule/user matches via rule/evaluate flag with detail +evaluation/parameterized/segment recursion/simple single-level recursion/simple single-level recursion/evaluate flag with detail +evaluation/parameterized/segment recursion/single level, two clauses in one rule, both match/single level, two clauses in one rule, both match/evaluate flag with detail +evaluation/parameterized/segment recursion/single level, two rules, one does not match/single level, two rules, one does not match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any empty array)/fallthrough/fallthrough/evaluate all flags +evaluation/parameterized/variation values that are falsy (any empty array)/fallthrough/fallthrough/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any empty array)/fallthrough/fallthrough/evaluate flag without detail +evaluation/parameterized/variation values that are falsy (any empty array)/off variation/off variation/evaluate all flags +evaluation/parameterized/variation values that are falsy (any empty array)/off variation/off variation/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any empty array)/off variation/off variation/evaluate flag without detail +evaluation/parameterized/variation values that are falsy (any empty array)/rule match/rule match/evaluate all flags +evaluation/parameterized/variation values that are falsy (any empty array)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any empty array)/rule match/rule match/evaluate flag without detail +evaluation/parameterized/variation values that are falsy (any empty array)/target match/target match/evaluate all flags +evaluation/parameterized/variation values that are falsy (any empty array)/target match/target match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any empty array)/target match/target match/evaluate flag without detail +evaluation/parameterized/variation values that are falsy (any empty string)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any false)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (any zero)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (bool)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (double)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (int)/rule match/rule match/evaluate flag with detail +evaluation/parameterized/variation values that are falsy (string)/rule match/rule match/evaluate flag with detail +events/context properties/custom attribute with value {}/debug event +events/context properties/custom attribute with value {}/identify event +events/context properties/custom attribute with value {}/index event from custom event +events/context properties/custom attribute with value {}/index event from evaluation +events/custom events/data and metricValue parameters/data={} +events/custom events/data and metricValue parameters/data={}, metricValue=-1.500000 +events/custom events/data and metricValue parameters/data={}, metricValue=0.000000 +events/custom events/data and metricValue parameters/data={}, metricValue=1.500000 diff --git a/examples/hello-debian/Dockerfile b/examples/hello-debian/Dockerfile index 4420278..f25cc3c 100644 --- a/examples/hello-debian/Dockerfile +++ b/examples/hello-debian/Dockerfile @@ -4,7 +4,7 @@ FROM debian:bookworm ARG VERSION=2.2.0 # {{ x-release-please-end }} -ARG CPP_SDK_VERSION=3.14.0 +ARG CPP_SDK_VERSION=3.15.0 # For unknown reasons, it appears that boost.json and boost.url aren't included in the # libboost-all package. diff --git a/examples/hello-haproxy/Dockerfile b/examples/hello-haproxy/Dockerfile index a4eba2d..467e7ee 100644 --- a/examples/hello-haproxy/Dockerfile +++ b/examples/hello-haproxy/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:22.04 ARG VERSION=2.2.0 # {{ x-release-please-end }} -ARG CPP_SDK_VERSION=3.14.0 +ARG CPP_SDK_VERSION=3.15.0 RUN apt-get update && apt-get install -y \ curl luarocks lua5.3 lua5.3-dev \ diff --git a/examples/hello-nginx/Dockerfile b/examples/hello-nginx/Dockerfile index 8a6eafb..74848f3 100644 --- a/examples/hello-nginx/Dockerfile +++ b/examples/hello-nginx/Dockerfile @@ -4,7 +4,7 @@ FROM openresty/openresty:1.31.8.1-0-jammy ARG VERSION=2.2.0 # {{ x-release-please-end }} -ARG CPP_SDK_VERSION=3.14.0 +ARG CPP_SDK_VERSION=3.15.0 RUN apt-get update && apt-get install -y \ git netbase curl libssl-dev apt-transport-https ca-certificates \ From f7a042f8f73fecca520d9a284ad57dede506aca6 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 9 Nov 2026 21:26:20 +0000 Subject: [PATCH 2/2] test: Suppress the new contract tests that check rule and prerequisite reasons Generated-by: termag --- contract-tests/test-suppressions.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contract-tests/test-suppressions.txt b/contract-tests/test-suppressions.txt index 5de6b7e..acc2caa 100644 --- a/contract-tests/test-suppressions.txt +++ b/contract-tests/test-suppressions.txt @@ -50,6 +50,29 @@ evaluation/parameterized/clause negation and value iteration/match if expression evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with any of multiple clause values/match if expression matches any of multiple context values with any of multiple clause values/evaluate flag with detail evaluation/parameterized/clause negation and value iteration/match if expression matches any of multiple context values with single clause value/match if expression matches any of multiple context values with single clause value/evaluate flag with detail evaluation/parameterized/clause negation and value iteration/negated, match if expression does not match single clause value/negated, match if expression does not match single clause value/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/flag with numeric key can be evaluated directly/flag with numeric key can be evaluated directly/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/prerequisite keys that denote the same number are distinct flags/prerequisite keys that denote the same number are distinct flags/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/segment excluded list does not exclude key with leading zeros/segment excluded list does not exclude key with leading zeros/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/segment excludedContexts does not exclude key with decimal point/segment excludedContexts does not exclude key with decimal point/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/segment included list matches identical key/segment included list matches identical key/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/segment includedContexts matches identical key/segment includedContexts matches identical key/evaluate flag with detail +evaluation/parameterized/keys are compared as exact strings/segment keys that denote the same number are distinct segments/segment keys that denote the same number are distinct segments/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite __class__ is not met/prerequisite __class__ is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite __index is not met/prerequisite __index is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite __proto__ is not met/prerequisite __proto__ is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite constructor is not met/prerequisite constructor is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite hasOwnProperty is not met/prerequisite hasOwnProperty is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite prototype is not met/prerequisite prototype is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite toString is not met/prerequisite toString is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - absent/prerequisite valueOf is not met/prerequisite valueOf is not met/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment __class__ matches an included key/segment __class__ matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment __index matches an included key/segment __index matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment __proto__ matches an included key/segment __proto__ matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment constructor matches an included key/segment constructor matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment hasOwnProperty matches an included key/segment hasOwnProperty matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment prototype matches an included key/segment prototype matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment toString matches an included key/segment toString matches an included key/evaluate flag with detail +evaluation/parameterized/keys like built-in object members - present/segment valueOf matches an included key/segment valueOf matches an included key/evaluate flag with detail evaluation/parameterized/numbers in exponent notation/large number equals itself/large number equals itself/evaluate flag with detail evaluation/parameterized/numbers in exponent notation/large number is greater than ordinary number/large number is greater than ordinary number/evaluate flag with detail evaluation/parameterized/numbers in exponent notation/large number is greater than smaller large number/large number is greater than smaller large number/evaluate flag with detail @@ -134,6 +157,9 @@ evaluation/parameterized/operators - equality (int)/user with single value match evaluation/parameterized/operators - equality (string)/user matches clause with single value/user matches clause with single value/evaluate flag with detail evaluation/parameterized/operators - equality (string)/user with multiple values matches clause with single value/user with multiple values matches clause with single value/evaluate flag with detail evaluation/parameterized/operators - equality (string)/user with single value matches clause with multiple values/user with single value matches clause with multiple values/evaluate flag with detail +evaluation/parameterized/operators - equality does not coerce strings and numbers/number 1000 matches number 1000/number 1000 matches number 1000/evaluate flag with detail +evaluation/parameterized/operators - equality does not coerce strings and numbers/string 007 matches string 007/string 007 matches string 007/evaluate flag with detail +evaluation/parameterized/operators - equality does not coerce strings and numbers/string 1e3 matches string 1e3 in a list/string 1e3 matches string 1e3 in a list/evaluate flag with detail evaluation/parameterized/operators - equality does not conflate booleans and numbers/boolean false matches boolean clause value false/boolean false matches boolean clause value false/evaluate flag with detail evaluation/parameterized/operators - equality does not conflate booleans and numbers/boolean true matches boolean clause value true/boolean true matches boolean clause value true/evaluate flag with detail evaluation/parameterized/operators - equality does not conflate booleans and numbers/integer 0 matches integer clause value 0/integer 0 matches integer clause value 0/evaluate flag with detail