From fa79fb7a9da8a09a8b1183884910b9fd1f4a893f Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 13:53:41 -0500 Subject: [PATCH 01/12] test: define mobile numeric and obfuscation fixture coverage --- AGENTS.md | 2 +- ci/sync-precomputed-fixtures.py | 142 ++++++++ ci/test_precomputed_validation.py | 62 ++++ ci/validate-precomputed-fixtures.py | 6 +- precomputed-assignments/README.md | 46 ++- .../cases/emissions/duplicate-evaluation.json | 13 - .../integer-precision-native-only.json | 10 +- .../cases/evaluator/numeric-float.json | 291 ++++++++++++++++ .../cases/evaluator/numeric-integer.json | 195 +++++++++++ .../cases/evaluator/numeric-number.json | 261 +++++++++++++++ .../evaluator/type-mismatch-all-types.json | 17 +- .../cases/obfuscation/obfuscation-keys.json | 259 +++++++++++++++ .../obfuscation-numeric-float.json | 310 ++++++++++++++++++ .../obfuscation-numeric-integer.json | 214 ++++++++++++ .../obfuscation-numeric-number.json | 280 ++++++++++++++++ ...uscation-reject-false-with-descriptor.json | 42 +++ ...obfuscation-reject-missing-descriptor.json | 38 +++ .../obfuscation-reject-missing-marker.json | 41 +++ .../obfuscation-reject-missing-salt.json | 41 +++ .../obfuscation-reject-nonhex-digest.json | 51 +++ .../obfuscation-reject-nonhex-salt.json | 42 +++ .../obfuscation-reject-null-descriptor.json | 39 +++ .../obfuscation-reject-null-marker.json | 38 +++ .../obfuscation-reject-null-salt.json | 42 +++ .../obfuscation-reject-plaintext.json | 51 +++ .../obfuscation-reject-short-digest.json | 51 +++ .../obfuscation-reject-short-salt.json | 42 +++ .../obfuscation-reject-string-marker.json | 42 +++ .../obfuscation-reject-unknown-scheme.json | 42 +++ .../obfuscation-reject-uppercase-digest.json | 51 +++ .../obfuscation-reject-uppercase-salt.json | 42 +++ .../obfuscation/obfuscation-rotated-salt.json | 259 +++++++++++++++ .../obfuscation-unsupported-consumer.json | 47 +++ schemas/precomputed-assignment.schema.json | 141 ++++++-- 34 files changed, 3201 insertions(+), 49 deletions(-) create mode 100644 ci/sync-precomputed-fixtures.py create mode 100644 ci/test_precomputed_validation.py create mode 100644 precomputed-assignments/cases/evaluator/numeric-float.json create mode 100644 precomputed-assignments/cases/evaluator/numeric-integer.json create mode 100644 precomputed-assignments/cases/evaluator/numeric-number.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-keys.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-rotated-salt.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-unsupported-consumer.json diff --git a/AGENTS.md b/AGENTS.md index f7bccf06..d75dac2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -140,7 +140,7 @@ Always `[{"platform": "", "reason": ""}]`. Mutually exclusive. Default (neither present): applies to all platforms. Valid platforms: `web`, `ios`, `android`, `react-native`, `flutter`, `maui`, -`kotlin-multiplatform`, `electron`. +`kotlin-multiplatform`, `electron`, `unity`. ## Error codes diff --git a/ci/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py new file mode 100644 index 00000000..addd7ec4 --- /dev/null +++ b/ci/sync-precomputed-fixtures.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Import a reproducible precompute test snapshot; never edit imported JSON.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import re +import subprocess +import tempfile + +REPOSITORY = 'https://github.com/DataDog/ffe-system-test-data.git' +PREFIX = 'precomputed-assignments/' +FILES = { + 'schemas/precomputed-assignment.schema.json': 'schema.json', + 'precomputed-assignments/README.md': 'README.md', + 'LICENSE': 'LICENSE', + 'NOTICE': 'NOTICE', +} + + +def git(source, *args): + return subprocess.check_output(['git', '-C', str(source), *args]) + + +def snapshot(source, ref): + if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._/-]*', ref) or '..' in ref: + raise ValueError('Invalid fixture revision') + sha = git(source, 'rev-parse', '--verify', ref + '^{commit}').decode().strip() + if not re.fullmatch(r'[0-9a-f]{40}', sha): + raise ValueError('Expected a full source commit SHA') + result = {} + for entry in git(source, 'ls-tree', '-rz', '--full-tree', sha).split(b'\0'): + if not entry: + continue + metadata, raw_path = entry.split(b'\t', 1) + path = raw_path.decode() + target = FILES.get(path) + if path.startswith(PREFIX + 'cases/') and path.endswith('.json'): + target = path[len(PREFIX):] + if target is None: + continue + mode, kind, blob = metadata.decode().split() + if mode != '100644' or kind != 'blob': + raise ValueError('Fixture entries must be ordinary files: ' + path) + result[target] = git(source, 'cat-file', 'blob', blob) + cases = sorted(p for p in result if p.startswith('cases/')) + if not cases or not all(p in result for p in FILES.values()): + raise ValueError('Missing or empty precomputed fixture corpus') + for path in cases: + data = json.loads(result[path]) + if not isinstance(data, dict) or data.get('name') != Path(path).stem: + raise ValueError('Invalid fixture identity: ' + path) + if not isinstance(data.get('evaluations'), list): + raise ValueError('Missing evaluations: ' + path) + result['index.json'] = (json.dumps(cases, indent=2) + '\n').encode() + result['SOURCE.json'] = (json.dumps({ + 'repository': REPOSITORY, + 'commit': sha, + 'files': {p: hashlib.sha256(v).hexdigest() for p, v in sorted(result.items())}, + }, indent=2) + '\n').encode() + return result + + +def read_snapshot(destination): + if not destination.exists(): + return {} + result = {} + for path in destination.rglob('*'): + if path.is_symlink(): + raise ValueError('Snapshot must not contain symbolic links') + if path.is_file() and path.suffix != '.meta': + result[path.relative_to(destination).as_posix()] = path.read_bytes() + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--ref', default='main') + parser.add_argument('--source', type=Path, help='Read a local Git checkout at the requested committed revision') + parser.add_argument('--check', action='store_true', help='Compare with the recorded upstream SHA; do not write') + args = parser.parse_args() + root = Path(__file__).resolve().parent.parent + config = json.loads((root / 'ci/precomputed-fixtures.json').read_text()) + relative = Path(config['destination']) + if relative.is_absolute() or '..' in relative.parts: + raise ValueError('Destination must be inside the repository') + destination = root / relative + if not destination.resolve().is_relative_to(root): + raise ValueError('Destination escapes the repository') + if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._/-]*', args.ref) or '..' in args.ref: + raise ValueError('Invalid fixture revision') + before = read_snapshot(destination) + ref = json.loads(before['SOURCE.json'])['commit'] if args.check else args.ref + with tempfile.TemporaryDirectory(prefix='ffe-precompute-') as temporary: + source = args.source + if source is None: + source = Path(temporary) / 'source' + subprocess.run(['git', 'init', '-q', str(source)], check=True) + subprocess.run(['git', '-C', str(source), 'fetch', '-q', '--depth=1', REPOSITORY, ref], check=True) + ref = 'FETCH_HEAD' + after = snapshot(source, ref) + changed = {p:v for p,v in before.items() if p != 'SOURCE.json'} != {p:v for p,v in after.items() if p != 'SOURCE.json'} + if args.check: + if before != after: + raise ValueError('Snapshot differs from its recorded source commit') + elif changed: + destination.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix='.precompute-', dir=destination.parent) as staging: + staged = Path(staging) / 'snapshot' + staged.mkdir() + for path, content in after.items(): + output = staged / path + output.parent.mkdir(parents=True, exist_ok=True) + output.write_bytes(content) + # Preserve Unity's existing asset identities when refreshing a snapshot. + if destination.exists(): + for meta in destination.rglob('*.meta'): + rel = meta.relative_to(destination) + asset = staged / str(rel)[:-5] + if asset.exists(): + (staged / rel).write_bytes(meta.read_bytes()) + backup = Path(staging) / 'previous' + destination.rename(backup) + try: + staged.rename(destination) + except BaseException: + backup.rename(destination) + raise + else: + staged.rename(destination) + selected = before if args.check or not changed else after + metadata = json.loads(selected['SOURCE.json']) + count = len(json.loads(selected['index.json'])) + print(f"FFE precomputed fixtures: {metadata['commit']}; {count} files; changed={str(changed).lower()}") + if os.environ.get('GITHUB_OUTPUT'): + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: + output.write(f"changed={str(changed).lower()}\nsource_commit={metadata['commit']}\nfixture_count={count}\n") + + +if __name__ == '__main__': + main() diff --git a/ci/test_precomputed_validation.py b/ci/test_precomputed_validation.py new file mode 100644 index 00000000..7336bb10 --- /dev/null +++ b/ci/test_precomputed_validation.py @@ -0,0 +1,62 @@ +import copy +import importlib.util +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parent.parent +SPEC = importlib.util.spec_from_file_location('validator', ROOT / 'ci/validate-precomputed-fixtures.py') +validator = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(validator) + + +class PrecomputedValidationTest(unittest.TestCase): + def setUp(self): + self.schema = json.loads((ROOT / 'schemas/precomputed-assignment.schema.json').read_text()) + self.case = json.loads((ROOT / 'precomputed-assignments/cases/evaluator/numeric-number.json').read_text()) + self.directory = tempfile.TemporaryDirectory() + self.addCleanup(self.directory.cleanup) + + def validate(self, value): + path = Path(self.directory.name) / 'numeric-number.json' + path.write_text(json.dumps(value)) + return validator.validate_fixture(path, self.schema) + + def test_valid_numeric_matrix(self): + self.assertEqual([], self.validate(self.case)) + + def test_unknown_platform_fails(self): + self.case['_skip'] = [{'platform': 'andriod'}] + self.assertTrue(self.validate(self.case)) + + def test_rejected_response_cannot_also_evaluate(self): + self.case['expectedResponseError'] = 'INVALID_RESPONSE' + self.assertTrue(self.validate(self.case)) + + def test_empty_evaluations_require_rejection(self): + self.case['evaluations'] = [] + self.assertTrue(self.validate(self.case)) + self.case['expectedResponseError'] = 'INVALID_RESPONSE' + self.assertEqual([], self.validate(self.case)) + + def test_non_object_is_reported_not_crashed(self): + self.assertTrue(self.validate([])) + + def test_new_nested_fixture_is_discovered(self): + path = Path(self.directory.name) / 'new' / 'nested' / 'numeric-number.json' + path.parent.mkdir(parents=True) + path.write_text(json.dumps(self.case)) + self.assertEqual([path], validator.find_fixtures(Path(self.directory.name))) + + def test_empty_directory_fails(self): + result = subprocess.run([sys.executable, str(ROOT / 'ci/validate-precomputed-fixtures.py'), + '--schema', str(ROOT / 'schemas/precomputed-assignment.schema.json'), self.directory.name], + capture_output=True) + self.assertEqual(1, result.returncode) + + +if __name__ == '__main__': + unittest.main() diff --git a/ci/validate-precomputed-fixtures.py b/ci/validate-precomputed-fixtures.py index ac316e3f..88ab2f48 100755 --- a/ci/validate-precomputed-fixtures.py +++ b/ci/validate-precomputed-fixtures.py @@ -132,6 +132,8 @@ def validate_fixture(filepath, schema): errors.append(f"{rel}: schema: {json_path}: {error.message}") # Custom checks + if not isinstance(data, dict): + return errors errors.extend(f"{rel}: {e}" for e in check_skip_include_mutual_exclusivity(data)) errors.extend(f"{rel}: {e}" for e in check_matcher_counts(data)) errors.extend(f"{rel}: {e}" for e in check_name_matches_filename(data, filepath)) @@ -171,8 +173,8 @@ def main(): fixtures = find_fixtures(fixture_dir) if not fixtures: - print(f"WARNING: no .json files found in {fixture_dir}", file=sys.stderr) - sys.exit(0) + print(f"ERROR: no .json files found in {fixture_dir}", file=sys.stderr) + sys.exit(1) all_errors = [] valid_count = 0 diff --git a/precomputed-assignments/README.md b/precomputed-assignments/README.md index d67a0fe0..2cda7db9 100644 --- a/precomputed-assignments/README.md +++ b/precomputed-assignments/README.md @@ -141,7 +141,7 @@ regardless of where they appear (case, evaluation, or matcher level). - `reason` is optional. Valid platform identifiers: `web`, `ios`, `android`, `react-native`, `flutter`, -`maui`, `kotlin-multiplatform`, `electron`. +`maui`, `kotlin-multiplatform`, `electron`, `unity`. ## How to implement a test runner @@ -177,3 +177,47 @@ Validate fixtures locally with: ```bash python3 ci/validate-precomputed-fixtures.py ``` + +## Mobile conformance and numeric types + +Native Integer getters accept `integer`. Native Double getters accept `float`. +The declared type controls this decision even when a `float` value is whole. +Legacy `number` accepts Double reads and representable integral Integer reads. +Fractional Integer reads return the default with `TYPE_MISMATCH`. +Single-Number APIs use the explicit evaluation filters for cross-type tests. + +Discover cases recursively. Do not maintain a filename list. Fail when the +corpus is missing or empty. Report the fixture revision, executed evaluations, +and exclusions. New JSON files require a snapshot refresh, not runner edits. +The generated `index.json` supports environments that cannot enumerate packaged +resources. It must be regenerated from the complete directory, never edited. + +`expectedResponseError: "INVALID_RESPONSE"` requires rejection of the complete +response before installation. Such a case has no evaluations. Assert rejection +explicitly; do not catch every exception as a successful expected rejection. + +The obfuscation cases use `flag-key-sha256-v1`. Hash the UTF-8 domain prefix +`datadog.feature-flags.flag-key.v1\0`, the decoded 16-byte salt, and the unchanged +UTF-8 flag key. Digest vectors are fixed JSON data. Never derive an expected +digest using the SDK being tested. Lookup uses the caller's plaintext key; +emitted events retain that plaintext identity. Missing encoded lookups must not +retry plaintext lookup. Unsupported consumers must reject encoded responses. + +The corpus checks duplicate evaluations after one flush. Aggregating consumers +emit one event with `evaluation_count: 2`. Exposure deduplication is independent. +Runners must inspect real event records, not synthesize them from expectations. +Keep native bridge and deployed-backend proof separate from mocked conformance. + +## Snapshot maintenance + +Consumers can copy `ci/sync-precomputed-fixtures.py` and configure the destination +in `ci/precomputed-fixtures.json`. The importer copies committed source bytes, +records the full SHA and file digests in `SOURCE.json`, generates `index.json`, +and removes deleted cases. `--check` compares the complete snapshot with its +recorded source commit. `--source` permits testing a committed local fixture +branch before publication. Tests use the checked-in snapshot without networking. + +Fixture PRs can expose existing SDK defects. Record those failures rather than +changing expectations or adding local skips. Platform filters describe API +applicability, not temporary implementation failures. `unity` is the identifier +for the Unity Flags implementation. diff --git a/precomputed-assignments/cases/emissions/duplicate-evaluation.json b/precomputed-assignments/cases/emissions/duplicate-evaluation.json index 25997a17..0e26bc82 100644 --- a/precomputed-assignments/cases/emissions/duplicate-evaluation.json +++ b/precomputed-assignments/cases/emissions/duplicate-evaluation.json @@ -54,21 +54,8 @@ } ], "evaluationEvents": [ - { - "_count": 2, - "_skip": [ - { - "platform": "web" - } - ] - }, { "_count": 1, - "_include": [ - { - "platform": "web" - } - ], "evaluation_count": 2 } ] diff --git a/precomputed-assignments/cases/evaluator/integer-precision-native-only.json b/precomputed-assignments/cases/evaluator/integer-precision-native-only.json index d6e24b70..a938cc69 100644 --- a/precomputed-assignments/cases/evaluator/integer-precision-native-only.json +++ b/precomputed-assignments/cases/evaluator/integer-precision-native-only.json @@ -48,14 +48,14 @@ } ] }, - "_skip": [ + "_include": [ { - "reason": "JavaScript cannot represent integers beyond 2^53-1 without loss of precision. This fixture uses a value outside the safe JavaScript integer range.", - "platform": "web" + "platform": "ios", + "reason": "The native integer API preserves signed 64-bit integers. Browser Dart must report separate applicability." }, { - "reason": "The Android public integer flag evaluation API currently uses Int32 and cannot represent this value. Revisit when the API supports Long.", - "platform": "android" + "platform": "flutter", + "reason": "The native integer API preserves signed 64-bit integers. Browser Dart must report separate applicability." } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-float.json b/precomputed-assignments/cases/evaluator/numeric-float.json new file mode 100644 index 00000000..4bbd9d3a --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-float.json @@ -0,0 +1,291 @@ +{ + "name": "numeric-float", + "description": "numeric float.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "float-0-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-0-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "evaluations": [ + { + "flag": "float-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 5, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/precomputed-assignments/cases/evaluator/numeric-integer.json b/precomputed-assignments/cases/evaluator/numeric-integer.json new file mode 100644 index 00000000..381cfa53 --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-integer.json @@ -0,0 +1,195 @@ +{ + "name": "numeric-integer", + "description": "numeric integer.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "integer-0-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer-0-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer-5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer-5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer--5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer--5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "evaluations": [ + { + "flag": "integer-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 6, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 3, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/precomputed-assignments/cases/evaluator/numeric-number.json b/precomputed-assignments/cases/evaluator/numeric-number.json new file mode 100644 index 00000000..946151c6 --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-number.json @@ -0,0 +1,261 @@ +{ + "name": "numeric-number", + "description": "numeric number.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "number-0-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-0-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "evaluations": [ + { + "flag": "number-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json b/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json index a2f0dc68..2fd77213 100644 --- a/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json +++ b/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json @@ -1,6 +1,6 @@ { "name": "type-mismatch-all-types", - "description": "Each flag is evaluated as the wrong type. All must return their default value with a typeMismatch error. Note: the integer-as-float case (fixture.mismatch.int-as-float) is SDK-implementation-dependent \u2014 SDKs that allow implicit int-to-float promotion (Java, Swift, Dart) will not return typeMismatch for that evaluation. Add evaluation-level skipForSdks entries for those platforms once verified.", + "description": "Wrong typed getters return the caller default with TYPE_MISMATCH. APIs with one Number getter do not implement integer-versus-float mismatch.", "context": { "targetingKey": "mismatch-user", "attributes": {} @@ -91,8 +91,12 @@ }, "_skip": [ { - "reason": "JavaScript does not distinguish integer from float; the web SDK uses getNumberDetails for both types and does not raise typeMismatch for integer-as-float evaluations.", - "platform": "web" + "platform": "web", + "reason": "The public API exposes one Number getter." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter." } ] }, @@ -126,7 +130,12 @@ "_count": 5, "_skip": [ { - "platform": "web" + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-keys.json b/precomputed-assignments/cases/obfuscation/obfuscation-keys.json new file mode 100644 index 00000000..9c1be0a3 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-keys.json @@ -0,0 +1,259 @@ +{ + "name": "obfuscation-keys", + "description": "obfuscation keys.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "9817872c144b018abd77e3915bd77e2c27f4f534dccff8ffaca27361e3a5e1ee": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-0", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "adca75d2141c51b0c0f084c1058edfb8e6e763f91aaf54ca06326d9847bd9586": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-1", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b1a5f851cc82a3fdf03a461d72a2241584dcf455df1d384e02a937901b3bc80b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-2", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3bfa8c3c17c1b61035b98ecf14007cdf5cba5ce61a48e8cfb65f8106541892d3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-3", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1e8b7ec5e8028a1ec96b38dd37f6ccea041c0a90358904af40ac5810c23c8765": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-4", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "94b611e0d3b26b52f6ad66013d1c72f8a92ea109390049759e75d3c8d3aa4dab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-5", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bac134d201be5e7f28fc7019248f0809c2a013b137e866446ee71add2bc344c7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-6", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "072d985b427f536ad0a11b2d4c5e0f7e6f0ff6d23e779e082f75599ce3fe3eba": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-7", + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "flag", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-0", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "Flag", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-1", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": " flag ", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-2", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "caf\u00e9", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-3", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "cafe\u0301", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-4", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "\ud83d\udeb2/\u65d7", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-5", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "a\u0000b", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-6", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-7", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "9817872c144b018abd77e3915bd77e2c27f4f534dccff8ffaca27361e3a5e1ee", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "fallback", + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "flag" + } + }, + { + "_count": 1, + "flag": { + "key": "Flag" + } + }, + { + "_count": 1, + "flag": { + "key": " flag " + } + }, + { + "_count": 1, + "flag": { + "key": "caf\u00e9" + } + }, + { + "_count": 1, + "flag": { + "key": "cafe\u0301" + } + }, + { + "_count": 1, + "flag": { + "key": "\ud83d\udeb2/\u65d7" + } + }, + { + "_count": 1, + "flag": { + "key": "a\u0000b" + } + }, + { + "_count": 1, + "flag": { + "key": "" + } + } + ], + "evaluationEvents": [ + { + "_count": 9 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json new file mode 100644 index 00000000..6547f39a --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json @@ -0,0 +1,310 @@ +{ + "name": "obfuscation-numeric-float", + "description": "numeric float.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "4fc70afd0edbe9f6c3605f50b73892f4167f395a93aa5c2e91c5380818270237": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "01c7520392b183ba08426484c8981ef7b9574d8d5e1f0b5b0afc04945b1fdc65": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "30a0b6a28e18bbdd712d677df8b0a0012086219260528d4967214fa45f36f90b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "b432d3d8d83d0f10acfb8d8099c7dd1238b96b45e12f2407ae6bfd938ad92354": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "26a957788e3e02fda8ace878f452615d85c2d80a539a19522b45c9fac903b905": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "fd7b22e26bc6227562127dc3945e30e159e1b522ba003b52706d5b33c8db0519": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "93cafc55f2224734ccd9a3a0030a8bd007dcded0f48320a16d2044744cc6a4f6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "5ad25a909e7b8ef40cf13c71a7ec8f2ab4bb8a3ae4a5439029357f9982508bf8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "4d677ea458fe5ed9851dbdba4f92981a432b5b9b467445032483b9fa2d2f7368": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "eb98f328fb6978a74722d54badcaf0335bc7433ca86e205ecca0be0ac86012c8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "float-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 5, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json new file mode 100644 index 00000000..7380abfe --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json @@ -0,0 +1,214 @@ +{ + "name": "obfuscation-numeric-integer", + "description": "numeric integer.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "3a56e171b0efb081585be54227ac1792ebae79900fb0e0ee2c3850ebc7dd7fd0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "3df507231bc602ed78f6e01db4497a88b33c086a455deba043f35dde999c0b7d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "58e7c159ddb85fe9250395e7797d80ecb7dca88e1a22767261a9482611baa358": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "a4ef458efa0a3876a8509351d70fa007a68460b21c64cc64680c1099ec450dab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "01c7f586a7f7bd98d4cbc75760f506b6ff8a9ba7195e88f975a708cc7b6ebb30": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "14a559f9d240120a5038b7f3f6529a0cba11d3cbc84409c84678290f4116b809": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "integer-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 6, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 3, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json new file mode 100644 index 00000000..9e54ced9 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json @@ -0,0 +1,280 @@ +{ + "name": "obfuscation-numeric-number", + "description": "numeric number.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "085fa5b25a3d42952668a59d052deb6ee5bc2418a352b9887556fe862ef78ca8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "66aeb85f02e96149bd485b28375161c4bc154e748545593a40da549d53f479c2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "eef7048e9d655ec6f29019eec43986e1d4ba6ff42523ecd8f46b6ac0ae883050": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "a46a4fdb29e1de92315dc1ee481f59a33455841d317e38191f13cdcdd9fd2eb0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "5f25ccca362e35bf27475aa056da166b4e7ecf6ed66b65bc25f9d573717d2628": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "914ae5836aae14f5bd8348d1669e008e400c43887d08cd6dc67b028d914e8c6a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "3cecd82b1b8e3c49bc3fe1886fdd0a57de07ac68a437f59bf7c6c6317d83b03b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "5a539f4ac96a34becffeed98230a919b3082e085bb79deed40d88600d5500636": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "ec97872ee38dc677992d1df24af2093298d35a35d246ea3dd39df7e33279b358": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "4b33526589906dcabeefa0e880ad6697d4e6673af26187c67947124777a67bbc": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "number-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json new file mode 100644 index 00000000..bf07eb08 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-false-with-descriptor", + "description": "obfuscation reject false with descriptor.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": false, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json new file mode 100644 index 00000000..7591f252 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json @@ -0,0 +1,38 @@ +{ + "name": "obfuscation-reject-missing-descriptor", + "description": "obfuscation reject missing descriptor.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json new file mode 100644 index 00000000..517639b6 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json @@ -0,0 +1,41 @@ +{ + "name": "obfuscation-reject-missing-marker", + "description": "obfuscation reject missing marker.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json new file mode 100644 index 00000000..c80c02d8 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json @@ -0,0 +1,41 @@ +{ + "name": "obfuscation-reject-missing-salt", + "description": "obfuscation reject missing salt.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json new file mode 100644 index 00000000..1a9f0997 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json @@ -0,0 +1,51 @@ +{ + "name": "obfuscation-reject-nonhex-digest", + "description": "obfuscation reject nonhex digest.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json new file mode 100644 index 00000000..3f25b3ad --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-nonhex-salt", + "description": "obfuscation reject nonhex salt.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json new file mode 100644 index 00000000..ec1df1a3 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json @@ -0,0 +1,39 @@ +{ + "name": "obfuscation-reject-null-descriptor", + "description": "obfuscation reject null descriptor.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": null + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json new file mode 100644 index 00000000..42115c62 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json @@ -0,0 +1,38 @@ +{ + "name": "obfuscation-reject-null-marker", + "description": "obfuscation reject null marker.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": null + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json new file mode 100644 index 00000000..e14c20a6 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-null-salt", + "description": "obfuscation reject null salt.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": null + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json new file mode 100644 index 00000000..c741b3a3 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json @@ -0,0 +1,51 @@ +{ + "name": "obfuscation-reject-plaintext", + "description": "obfuscation reject plaintext.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "flag": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json new file mode 100644 index 00000000..1564d22a --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json @@ -0,0 +1,51 @@ +{ + "name": "obfuscation-reject-short-digest", + "description": "obfuscation reject short digest.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json new file mode 100644 index 00000000..ac326b4f --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-short-salt", + "description": "obfuscation reject short salt.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json new file mode 100644 index 00000000..3c71e026 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-string-marker", + "description": "obfuscation reject string marker.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": "true", + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json new file mode 100644 index 00000000..63845331 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-unknown-scheme", + "description": "obfuscation reject unknown scheme.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "unknown", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json new file mode 100644 index 00000000..6602d3eb --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json @@ -0,0 +1,51 @@ +{ + "name": "obfuscation-reject-uppercase-digest", + "description": "obfuscation reject uppercase digest.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json new file mode 100644 index 00000000..3e524a02 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json @@ -0,0 +1,42 @@ +{ + "name": "obfuscation-reject-uppercase-salt", + "description": "obfuscation reject uppercase salt.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090A0B0C0D0E0F" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-rotated-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-rotated-salt.json new file mode 100644 index 00000000..ba16c9a0 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-rotated-salt.json @@ -0,0 +1,259 @@ +{ + "name": "obfuscation-rotated-salt", + "description": "obfuscation rotated salt.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "e9da87d1d051b4e9ae47e00e97c87a11d939a8746280eed92f475a9fb495ae18": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-0", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bb0951b2113f60f04611546a5ec4c7e7e12cabc968eff59362aaeecc6d683fa9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-1", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bd79ba8d71066ff40da24e6ed335f7669113d17fef540cde96044c03411af1c7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-2", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a2d89601f57c605c641d06d9bbe736d7e7873744ada2beecff36c051d67c1114": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-3", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "65675bf54578b33d06926dc6dbb43abe39d12458b7f7202d72d22d3cf294e63d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-4", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2d84852642a347b30811707354eed8d3ea564967f46d549be94b4c58e53c78c8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-5", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bd982551b4c9c73d5a4d857288e0889e2641a71ea2be43767284d29348399139": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-6", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f43ee46b2cdf25f71c542e857876b885b68295fe1845d7f3ea83f32f334c276c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "string", + "variationValue": "value-7", + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "ffffffffffffffffffffffffffffffff" + } + } + } + }, + "evaluations": [ + { + "flag": "flag", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-0", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "Flag", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-1", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": " flag ", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-2", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "caf\u00e9", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-3", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "cafe\u0301", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-4", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "\ud83d\udeb2/\u65d7", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-5", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "a\u0000b", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-6", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "value-7", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "e9da87d1d051b4e9ae47e00e97c87a11d939a8746280eed92f475a9fb495ae18", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "fallback", + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "flag" + } + }, + { + "_count": 1, + "flag": { + "key": "Flag" + } + }, + { + "_count": 1, + "flag": { + "key": " flag " + } + }, + { + "_count": 1, + "flag": { + "key": "caf\u00e9" + } + }, + { + "_count": 1, + "flag": { + "key": "cafe\u0301" + } + }, + { + "_count": 1, + "flag": { + "key": "\ud83d\udeb2/\u65d7" + } + }, + { + "_count": 1, + "flag": { + "key": "a\u0000b" + } + }, + { + "_count": 1, + "flag": { + "key": "" + } + } + ], + "evaluationEvents": [ + { + "_count": 9 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-unsupported-consumer.json b/precomputed-assignments/cases/obfuscation/obfuscation-unsupported-consumer.json new file mode 100644 index 00000000..94894f1b --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-unsupported-consumer.json @@ -0,0 +1,47 @@ +{ + "name": "obfuscation-unsupported-consumer", + "description": "obfuscation unsupported consumer.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "9817872c144b018abd77e3915bd77e2c27f4f534dccff8ffaca27361e3a5e1ee": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [] + }, + "_include": [ + { + "platform": "react-native", + "reason": "Encoded assignments are unsupported; reject rather than install digest keys." + }, + { + "platform": "unity", + "reason": "Encoded assignments are unsupported; reject rather than install digest keys." + } + ], + "expectedResponseError": "INVALID_RESPONSE" +} diff --git a/schemas/precomputed-assignment.schema.json b/schemas/precomputed-assignment.schema.json index 822a6413..5aac5dce 100644 --- a/schemas/precomputed-assignment.schema.json +++ b/schemas/precomputed-assignment.schema.json @@ -5,7 +5,14 @@ "description": "Schema for precomputed assignment test fixtures consumed by client SDKs.", "type": "object", "additionalProperties": false, - "required": ["name", "description", "context", "response", "evaluations", "expectations"], + "required": [ + "name", + "description", + "context", + "response", + "evaluations", + "expectations" + ], "properties": { "name": { "type": "string", @@ -26,8 +33,7 @@ "type": "array", "items": { "$ref": "#/$defs/Evaluation" - }, - "minItems": 1 + } }, "expectations": { "$ref": "#/$defs/Expectations" @@ -39,20 +45,40 @@ "_include": { "$ref": "#/$defs/PlatformFilter", "description": "Run this fixture only for listed platforms." + }, + "expectedResponseError": { + "const": "INVALID_RESPONSE", + "description": "Reject the complete response before installation; run no evaluations and emit no events." } }, "not": { - "required": ["_skip", "_include"] + "required": [ + "_skip", + "_include" + ] }, "$defs": { "PlatformFilterEntry": { "type": "object", - "required": ["platform"], + "required": [ + "platform" + ], "additionalProperties": false, "properties": { "platform": { "type": "string", - "description": "Platform identifier (e.g., web, ios, android, flutter, react-native, maui, kotlin-multiplatform, electron)." + "description": "Platform identifier (e.g., web, ios, android, flutter, react-native, maui, kotlin-multiplatform, electron).", + "enum": [ + "web", + "ios", + "android", + "react-native", + "flutter", + "unity", + "maui", + "kotlin-multiplatform", + "electron" + ] }, "reason": { "type": "string", @@ -69,11 +95,17 @@ }, "EvaluationContext": { "type": "object", - "required": ["targetingKey", "attributes"], + "required": [ + "targetingKey", + "attributes" + ], "additionalProperties": false, "properties": { "targetingKey": { - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "attributes": { "type": "object" @@ -82,29 +114,45 @@ }, "ExpectedResult": { "type": "object", - "required": ["value"], + "required": [ + "value" + ], "additionalProperties": false, "properties": { "value": { "description": "Expected flag value; must match the evaluation variationType." }, "reason": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "Expected OpenFeature reason code." }, "errorCode": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "Expected OpenFeature error code." }, "variantKey": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "Expected variation key." } } }, "Evaluation": { "type": "object", - "required": ["flag", "variationType", "defaultValue", "expectedResult"], + "required": [ + "flag", + "variationType", + "defaultValue", + "expectedResult" + ], "additionalProperties": false, "properties": { "flag": { @@ -113,7 +161,13 @@ }, "variationType": { "type": "string", - "enum": ["boolean", "string", "integer", "float", "object"], + "enum": [ + "boolean", + "string", + "integer", + "float", + "object" + ], "description": "Type to request from the SDK getter." }, "defaultValue": { @@ -137,7 +191,9 @@ "const": "boolean" } }, - "required": ["variationType"] + "required": [ + "variationType" + ] }, "then": { "properties": { @@ -161,7 +217,9 @@ "const": "string" } }, - "required": ["variationType"] + "required": [ + "variationType" + ] }, "then": { "properties": { @@ -185,7 +243,9 @@ "const": "integer" } }, - "required": ["variationType"] + "required": [ + "variationType" + ] }, "then": { "properties": { @@ -209,7 +269,9 @@ "const": "float" } }, - "required": ["variationType"] + "required": [ + "variationType" + ] }, "then": { "properties": { @@ -233,7 +295,9 @@ "const": "object" } }, - "required": ["variationType"] + "required": [ + "variationType" + ] }, "then": { "properties": { @@ -252,12 +316,17 @@ } ], "not": { - "required": ["_skip", "_include"] + "required": [ + "_skip", + "_include" + ] } }, "EventMatcher": { "type": "object", - "required": ["_count"], + "required": [ + "_count" + ], "description": "Matches received events by subset. All keys not prefixed with _ are match fields compared against event data. Control fields: _count (required), _skip, _include.", "properties": { "_count": { @@ -273,7 +342,10 @@ } }, "not": { - "required": ["_skip", "_include"] + "required": [ + "_skip", + "_include" + ] } }, "Expectations": { @@ -301,5 +373,28 @@ } } } - } + }, + "allOf": [ + { + "if": { + "required": [ + "expectedResponseError" + ] + }, + "then": { + "properties": { + "evaluations": { + "maxItems": 0 + } + } + }, + "else": { + "properties": { + "evaluations": { + "minItems": 1 + } + } + } + } + ] } From 3dc7774e66b2abed61caad5f9d7a39dccd63f4d7 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:07:21 -0500 Subject: [PATCH 02/12] test: verify fixture snapshots and define malformed flag isolation --- .../validate-precomputed-fixtures.yml | 5 ++ ci/sync-precomputed-fixtures.py | 33 ++++++++ ci/test_precomputed_sync.py | 78 +++++++++++++++++++ precomputed-assignments/README.md | 8 ++ .../defaults-and-emission-gates.json | 4 +- 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 ci/test_precomputed_sync.py diff --git a/.github/workflows/validate-precomputed-fixtures.yml b/.github/workflows/validate-precomputed-fixtures.yml index a232a580..b4f7cc8c 100644 --- a/.github/workflows/validate-precomputed-fixtures.yml +++ b/.github/workflows/validate-precomputed-fixtures.yml @@ -5,6 +5,8 @@ on: paths: - .github/workflows/validate-precomputed-fixtures.yml - ci/validate-precomputed-fixtures.py + - ci/test_precomputed*.py + - ci/sync-precomputed-fixtures.py - schemas/precomputed-assignment.schema.json - precomputed-assignments/** @@ -32,3 +34,6 @@ jobs: - name: Validate precomputed fixtures run: python3 ci/validate-precomputed-fixtures.py + + - name: Test fixture validation and snapshot import + run: python3 -m unittest discover -s ci -p 'test_precomputed*.py' diff --git a/ci/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py index addd7ec4..5c281ad5 100644 --- a/ci/sync-precomputed-fixtures.py +++ b/ci/sync-precomputed-fixtures.py @@ -8,6 +8,7 @@ import re import subprocess import tempfile +import uuid REPOSITORY = 'https://github.com/DataDog/ffe-system-test-data.git' PREFIX = 'precomputed-assignments/' @@ -74,10 +75,37 @@ def read_snapshot(destination): return result +def verify(destination): + content = read_snapshot(destination) + metadata = json.loads(content.pop('SOURCE.json')) + if metadata['repository'] != REPOSITORY or not re.fullmatch(r'[0-9a-f]{40}', metadata['commit']): + raise ValueError('Invalid source provenance') + hashes = {p: hashlib.sha256(v).hexdigest() for p, v in sorted(content.items())} + if hashes != metadata['files']: + raise ValueError('Snapshot content differs from its recorded checksums') + cases = sorted(p for p in content if p.startswith('cases/') and p.endswith('.json')) + if not cases or json.loads(content['index.json']) != cases: + raise ValueError('Missing, empty, or stale fixture index') + print(f"Verified {len(cases)} fixtures at {metadata['commit']}") + + +def unity_metadata(destination): + for asset in sorted(destination.rglob('*')): + if asset.suffix == '.meta': + continue + meta = asset.with_name(asset.name + '.meta') + if not meta.exists(): + guid = uuid.uuid5(uuid.NAMESPACE_URL, 'ffe-precomputed/' + asset.relative_to(destination).as_posix()).hex + folder = 'folderAsset: yes\n' if asset.is_dir() else '' + importer = 'DefaultImporter' if asset.is_dir() or asset.suffix != '.json' else 'TextScriptImporter' + meta.write_text(f'fileFormatVersion: 2\nguid: {guid}\n{folder}{importer}:\n externalObjects: {{}}\n userData: \n assetBundleName: \n assetBundleVariant: \n') + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--ref', default='main') parser.add_argument('--source', type=Path, help='Read a local Git checkout at the requested committed revision') + parser.add_argument('--verify', action='store_true', help='Verify local checksums and discovery index without network access') parser.add_argument('--check', action='store_true', help='Compare with the recorded upstream SHA; do not write') args = parser.parse_args() root = Path(__file__).resolve().parent.parent @@ -90,6 +118,9 @@ def main(): raise ValueError('Destination escapes the repository') if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._/-]*', args.ref) or '..' in args.ref: raise ValueError('Invalid fixture revision') + if args.verify: + verify(destination) + return before = read_snapshot(destination) ref = json.loads(before['SOURCE.json'])['commit'] if args.check else args.ref with tempfile.TemporaryDirectory(prefix='ffe-precompute-') as temporary: @@ -129,6 +160,8 @@ def main(): raise else: staged.rename(destination) + if config.get('unityMetadata') and not args.check: + unity_metadata(destination) selected = before if args.check or not changed else after metadata = json.loads(selected['SOURCE.json']) count = len(json.loads(selected['index.json'])) diff --git a/ci/test_precomputed_sync.py b/ci/test_precomputed_sync.py new file mode 100644 index 00000000..d541a0bf --- /dev/null +++ b/ci/test_precomputed_sync.py @@ -0,0 +1,78 @@ +"""Regression tests for generated fixture snapshots and recursive discovery.""" +import importlib.util +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + +spec = importlib.util.spec_from_file_location('sync', Path(__file__).with_name('sync-precomputed-fixtures.py')) +sync = importlib.util.module_from_spec(spec) +spec.loader.exec_module(sync) + + +class SnapshotTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.source = self.root / 'source' + self.source.mkdir() + subprocess.run(['git', 'init', '-q', self.source], check=True) + for path in sync.FILES: + target = self.source / path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text('{}') + case = self.source / 'precomputed-assignments/cases/new/nested/case.json' + case.parent.mkdir(parents=True) + case.write_text(json.dumps({'name': 'case', 'evaluations': []})) + subprocess.run(['git', '-C', self.source, 'add', '.'], check=True) + # Test repository identity only; no task commits or hooks are changed. + subprocess.run(['git', '-C', self.source, '-c', 'commit.gpgsign=false', '-c', 'core.hooksPath=/dev/null', + '-c', 'user.name=Fixture test', '-c', 'user.email=fixture@example.invalid', + 'commit', '-qm', 'fixture'], check=True) + self.destination = self.root / 'snapshot' + for path, value in sync.snapshot(self.source, 'HEAD').items(): + target = self.destination / path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(value) + + def test_nested_discovery_and_verification(self): + sync.verify(self.destination) + self.assertEqual(json.loads((self.destination / 'index.json').read_text()), ['cases/new/nested/case.json']) + + def test_modified_json_fails(self): + (self.destination / 'cases/new/nested/case.json').write_text('{}') + with self.assertRaises(ValueError): + sync.verify(self.destination) + + def test_missing_case_fails(self): + (self.destination / 'cases/new/nested/case.json').unlink() + with self.assertRaises(ValueError): + sync.verify(self.destination) + + def test_unindexed_case_fails(self): + (self.destination / 'cases/new.json').write_text('{}') + with self.assertRaises(ValueError): + sync.verify(self.destination) + + def test_symlink_rejected(self): + (self.destination / 'link').symlink_to(self.source) + with self.assertRaises(ValueError): + sync.verify(self.destination) + + def test_invalid_revision_rejected(self): + with self.assertRaises(ValueError): + sync.snapshot(self.source, '--help') + + def test_unity_guids_are_stable(self): + sync.unity_metadata(self.destination) + meta = self.destination / 'cases/new/nested/case.json.meta' + original = meta.read_text() + sync.unity_metadata(self.destination) + self.assertEqual(original, meta.read_text()) + sync.verify(self.destination) + + +if __name__ == '__main__': + unittest.main() diff --git a/precomputed-assignments/README.md b/precomputed-assignments/README.md index 2cda7db9..0a079116 100644 --- a/precomputed-assignments/README.md +++ b/precomputed-assignments/README.md @@ -221,3 +221,11 @@ Fixture PRs can expose existing SDK defects. Record those failures rather than changing expectations or adding local skips. Platform filters describe API applicability, not temporary implementation failures. `unity` is the identifier for the Unity Flags implementation. + +`--verify` checks local file digests and the discovery index without networking. +Use it in consumer CI. `--check` also verifies upstream provenance. For Unity, +set `unityMetadata: true` to preserve existing asset GUIDs and create missing metadata. + +Unsupported variation types are omitted independently. Their lookups return +`FLAG_NOT_FOUND`. A supported flag read with the wrong getter returns `TYPE_MISMATCH`. +Missing `extraLogging` is equivalent to an empty object. diff --git a/precomputed-assignments/cases/evaluator/defaults-and-emission-gates.json b/precomputed-assignments/cases/evaluator/defaults-and-emission-gates.json index 5ddbb6d5..bf906705 100644 --- a/precomputed-assignments/cases/evaluator/defaults-and-emission-gates.json +++ b/precomputed-assignments/cases/evaluator/defaults-and-emission-gates.json @@ -1,6 +1,6 @@ { "name": "defaults-and-emission-gates", - "description": "Precomputed assignments that validate exposure gates, unknown variation isolation, and default/error flagevaluation payloads.", + "description": "Precomputed assignments that validate exposure gates, unknown variation isolation, and default/error flagevaluation payloads. Unsupported variation types are omitted; lookup returns FLAG_NOT_FOUND.", "context": { "targetingKey": "precomputed-user", "attributes": { @@ -71,7 +71,7 @@ "expectedResult": { "value": false, "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", + "errorCode": "FLAG_NOT_FOUND", "variantKey": null } }, From e4f93740f783ddc0823be5f15daaf78c01869d48 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:13:51 -0500 Subject: [PATCH 03/12] test: require unsupported mobile consumers to reject invalid encodings --- .../obfuscation-reject-false-with-descriptor.json | 8 ++++++++ .../obfuscation-reject-missing-descriptor.json | 8 ++++++++ .../obfuscation/obfuscation-reject-missing-marker.json | 8 ++++++++ .../obfuscation/obfuscation-reject-missing-salt.json | 8 ++++++++ .../obfuscation/obfuscation-reject-nonhex-digest.json | 8 ++++++++ .../cases/obfuscation/obfuscation-reject-nonhex-salt.json | 8 ++++++++ .../obfuscation/obfuscation-reject-null-descriptor.json | 8 ++++++++ .../cases/obfuscation/obfuscation-reject-null-marker.json | 8 ++++++++ .../cases/obfuscation/obfuscation-reject-null-salt.json | 8 ++++++++ .../cases/obfuscation/obfuscation-reject-plaintext.json | 8 ++++++++ .../obfuscation/obfuscation-reject-short-digest.json | 8 ++++++++ .../cases/obfuscation/obfuscation-reject-short-salt.json | 8 ++++++++ .../obfuscation/obfuscation-reject-string-marker.json | 8 ++++++++ .../obfuscation/obfuscation-reject-unknown-scheme.json | 8 ++++++++ .../obfuscation/obfuscation-reject-uppercase-digest.json | 8 ++++++++ .../obfuscation/obfuscation-reject-uppercase-salt.json | 8 ++++++++ 16 files changed, 128 insertions(+) diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json index bf07eb08..40a03668 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json index 7591f252..0c0d8633 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json @@ -32,6 +32,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json index 517639b6..18741990 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json @@ -35,6 +35,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json index c80c02d8..ce837948 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json @@ -35,6 +35,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json index 1a9f0997..1cbb1a4e 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json @@ -45,6 +45,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json index 3f25b3ad..1c2c2ffb 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json index ec1df1a3..e1d27358 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json @@ -33,6 +33,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json index 42115c62..67a35537 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json @@ -32,6 +32,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json index e14c20a6..84350ae9 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json index c741b3a3..94059a26 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json @@ -45,6 +45,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json index 1564d22a..0e2f913c 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json @@ -45,6 +45,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json index ac326b4f..2b54a393 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json index 3c71e026..1c1a8bf9 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json index 63845331..7e98d9e8 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json index 6602d3eb..4d53d3e8 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json @@ -45,6 +45,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json index 3e524a02..2d89321f 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json @@ -36,6 +36,14 @@ { "platform": "flutter", "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "react-native", + "reason": "Reject invalid or unsupported encoding before installation." + }, + { + "platform": "unity", + "reason": "Reject invalid or unsupported encoding before installation." } ], "expectedResponseError": "INVALID_RESPONSE" From f8741c4f02fddc1bcf476321bb1df3b4cf477d74 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:22:15 -0500 Subject: [PATCH 04/12] test: report malformed fixtures without treating payload keys as filters --- ci/test_precomputed_validation.py | 9 +++++++++ ci/validate-precomputed-fixtures.py | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ci/test_precomputed_validation.py b/ci/test_precomputed_validation.py index 7336bb10..b2eb3862 100644 --- a/ci/test_precomputed_validation.py +++ b/ci/test_precomputed_validation.py @@ -45,6 +45,15 @@ def test_empty_evaluations_require_rejection(self): def test_non_object_is_reported_not_crashed(self): self.assertTrue(self.validate([])) + def test_malformed_expectations_are_reported_not_crashed(self): + for value in [None, [], {"exposures": None}, {"exposures": [None]}]: + self.case['expectations'] = value + self.assertTrue(self.validate(self.case)) + + def test_payload_keys_are_not_platform_filters(self): + self.case['response']['custom'] = {'_skip': 'payload', '_include': 'payload'} + self.assertEqual([], self.validate(self.case)) + def test_new_nested_fixture_is_discovered(self): path = Path(self.directory.name) / 'new' / 'nested' / 'numeric-number.json' path.parent.mkdir(parents=True) diff --git a/ci/validate-precomputed-fixtures.py b/ci/validate-precomputed-fixtures.py index 88ab2f48..79de11d8 100755 --- a/ci/validate-precomputed-fixtures.py +++ b/ci/validate-precomputed-fixtures.py @@ -48,6 +48,8 @@ def check_skip_include_mutual_exclusivity(obj, path="$"): if "_skip" in obj and "_include" in obj: errors.append(f"{path}: _skip and _include are mutually exclusive") for key, value in obj.items(): + if key in ("response", "context", "defaultValue", "expectedResult"): + continue errors.extend(check_skip_include_mutual_exclusivity(value, f"{path}.{key}")) elif isinstance(obj, list): for i, item in enumerate(obj): @@ -59,9 +61,15 @@ def check_matcher_counts(data, path="$"): """Check that all EventMatchers have _count >= 1.""" errors = [] expectations = data.get("expectations", {}) + if not isinstance(expectations, dict): + return errors for list_name in ("exposures", "evaluationEvents"): matchers = expectations.get(list_name, []) + if not isinstance(matchers, list): + continue for i, matcher in enumerate(matchers): + if not isinstance(matcher, dict): + continue mpath = f"{path}.expectations.{list_name}[{i}]" count = matcher.get("_count") if count is None: @@ -107,6 +115,8 @@ def check_skip_entry_format(obj, path="$"): f"{epath}: unexpected fields: {', '.join(sorted(extra))}" ) for key, value in obj.items(): + if key in ("response", "context", "defaultValue", "expectedResult"): + continue errors.extend(check_skip_entry_format(value, f"{path}.{key}")) elif isinstance(obj, list): for i, item in enumerate(obj): @@ -127,7 +137,7 @@ def validate_fixture(filepath, schema): # JSON Schema validation validator = jsonschema.Draft202012Validator(schema) - for error in sorted(validator.iter_errors(data), key=lambda e: list(e.path)): + for error in sorted(validator.iter_errors(data), key=lambda e: tuple(str(p) for p in e.path)): json_path = ".".join(str(p) for p in error.absolute_path) or "$" errors.append(f"{rel}: schema: {json_path}: {error.message}") From 743c4590371c82ccadd69a248b2b8c44ebfa524c Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:28:09 -0500 Subject: [PATCH 05/12] chore: preserve clean Unity snapshot metadata --- ci/sync-precomputed-fixtures.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ci/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py index 5c281ad5..d47e01c1 100644 --- a/ci/sync-precomputed-fixtures.py +++ b/ci/sync-precomputed-fixtures.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 +# Unless explicitly stated otherwise, all files in this repository are licensed under the +# Apache License Version 2.0. This product includes software developed at Datadog +# (https://www.datadoghq.com/). Copyright 2025-Present Datadog, Inc. """Import a reproducible precompute test snapshot; never edit imported JSON.""" import argparse import hashlib @@ -98,7 +101,7 @@ def unity_metadata(destination): guid = uuid.uuid5(uuid.NAMESPACE_URL, 'ffe-precomputed/' + asset.relative_to(destination).as_posix()).hex folder = 'folderAsset: yes\n' if asset.is_dir() else '' importer = 'DefaultImporter' if asset.is_dir() or asset.suffix != '.json' else 'TextScriptImporter' - meta.write_text(f'fileFormatVersion: 2\nguid: {guid}\n{folder}{importer}:\n externalObjects: {{}}\n userData: \n assetBundleName: \n assetBundleVariant: \n') + meta.write_text(f'fileFormatVersion: 2\nguid: {guid}\n{folder}{importer}:\n externalObjects: {{}}\n userData:\n assetBundleName:\n assetBundleVariant:\n') def main(): From fa245c05028576868b7e70bd06d4c90d1167690e Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 16:11:50 -0500 Subject: [PATCH 06/12] FFL-3568: Enforce lossless numeric reads across wire labels --- AGENTS.md | 2 +- precomputed-assignments/README.md | 15 +- .../evaluator/numeric-boundaries-float.json | 644 ++++++++++++++++++ .../evaluator/numeric-boundaries-integer.json | 644 ++++++++++++++++++ .../evaluator/numeric-boundaries-number.json | 644 ++++++++++++++++++ .../cases/evaluator/numeric-float.json | 260 ++++--- .../cases/evaluator/numeric-integer.json | 288 ++++++-- .../cases/evaluator/numeric-number.json | 216 ++++-- .../evaluator/type-mismatch-all-types.json | 39 +- .../obfuscation-numeric-float.json | 249 ++++--- .../obfuscation-numeric-integer.json | 277 ++++++-- .../obfuscation-numeric-number.json | 205 ++++-- 12 files changed, 3025 insertions(+), 458 deletions(-) create mode 100644 precomputed-assignments/cases/evaluator/numeric-boundaries-float.json create mode 100644 precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json create mode 100644 precomputed-assignments/cases/evaluator/numeric-boundaries-number.json diff --git a/AGENTS.md b/AGENTS.md index d75dac2f..af01e6ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,7 +147,7 @@ Valid platforms: `web`, `ios`, `android`, `react-native`, `flutter`, `maui`, | Code | When | |------|------| | `FLAG_NOT_FOUND` | Flag key not in response, or flag dropped due to malformed data | -| `TYPE_MISMATCH` | Requested type does not match `variationType` | +| `TYPE_MISMATCH` | Incompatible getter or numeric conversion loses information | | `PARSE_ERROR` | Enumerated field contains unexpected value | | `TARGETING_KEY_MISSING` | Evaluation requires targeting key but none provided | diff --git a/precomputed-assignments/README.md b/precomputed-assignments/README.md index 0a079116..cf76ab45 100644 --- a/precomputed-assignments/README.md +++ b/precomputed-assignments/README.md @@ -180,11 +180,16 @@ python3 ci/validate-precomputed-fixtures.py ## Mobile conformance and numeric types -Native Integer getters accept `integer`. Native Double getters accept `float`. -The declared type controls this decision even when a `float` value is whole. -Legacy `number` accepts Double reads and representable integral Integer reads. -Fractional Integer reads return the default with `TYPE_MISMATCH`. -Single-Number APIs use the explicit evaluation filters for cross-type tests. +Numeric reads depend on the value, not the `integer`, `float`, or `number` label. +Integer getters accept whole values within the accessor range. Double getters +accept numeric values without losing integer precision. Fractional Integer reads, +out-of-range reads, and inexact conversions return the default with `TYPE_MISMATCH`. +No new global safe-integer limit is imposed. React Native and web expose one Number +getter; Integer-only cases use explicit platform filters. Boundary fixtures cover +native 32-bit and 64-bit accessors separately. + +The precompute service retains `number`. No numeric capability is introduced. +See FFL-3568 and FFL-3564 for the lossless conversion decision. Discover cases recursively. Do not maintain a filename list. Fail when the corpus is missing or empty. Report the fixture revision, executed evaluations, diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json new file mode 100644 index 00000000..fccee380 --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json @@ -0,0 +1,644 @@ +{ + "name": "numeric-boundaries-float", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "numeric-boundaries-float-0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-12": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-13": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-14": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-15": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-16": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-17": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-18": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-19": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-20": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-float-21": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-float-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 15, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + } + ] + } + ], + "evaluationEvents": [ + { + "_count": 18, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + } + ] + }, + { + "_count": 13, + "_include": [ + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "android", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "flutter", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "unity", + "reason": "Requires native numeric precision or supported obfuscation." + } + ] +} diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json new file mode 100644 index 00000000..1f803e58 --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json @@ -0,0 +1,644 @@ +{ + "name": "numeric-boundaries-integer", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "numeric-boundaries-integer-0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-12": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-13": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-14": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-15": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-16": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-17": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-18": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-19": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-20": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-integer-21": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-integer-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 15, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + } + ] + } + ], + "evaluationEvents": [ + { + "_count": 18, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + } + ] + }, + { + "_count": 13, + "_include": [ + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "android", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "flutter", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "unity", + "reason": "Requires native numeric precision or supported obfuscation." + } + ] +} diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json new file mode 100644 index 00000000..4fad273a --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json @@ -0,0 +1,644 @@ +{ + "name": "numeric-boundaries-number", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "numeric-boundaries-number-0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-12": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-13": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-14": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-15": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-16": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-17": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-18": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-19": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-20": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-boundaries-number-21": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-number-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 15, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + } + ] + } + ], + "evaluationEvents": [ + { + "_count": 18, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + } + ] + }, + { + "_count": 13, + "_include": [ + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "android", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "flutter", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "unity", + "reason": "Requires native numeric precision or supported obfuscation." + } + ] +} diff --git a/precomputed-assignments/cases/evaluator/numeric-float.json b/precomputed-assignments/cases/evaluator/numeric-float.json index 4bbd9d3a..e6868d0e 100644 --- a/precomputed-assignments/cases/evaluator/numeric-float.json +++ b/precomputed-assignments/cases/evaluator/numeric-float.json @@ -1,6 +1,6 @@ { "name": "numeric-float", - "description": "numeric float.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,85 +10,101 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "float-0-as-integer": { + "numeric-float-0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-0-as-float": { + "numeric-float-1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-5-as-integer": { + "numeric-float-2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-5-as-float": { + "numeric-float-3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--5-as-integer": { + "numeric-float-4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--5-as-float": { + "numeric-float-5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-1.25-as-integer": { + "numeric-float-6": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-1.25-as-float": { + "numeric-float-7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--1.25-as-integer": { + "numeric-float-8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--1.25-as-float": { + "numeric-float-9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "numeric-float-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } } } @@ -96,28 +112,18 @@ }, "evaluations": [ { - "flag": "float-0-as-integer", + "flag": "numeric-float-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-0-as-float", + "flag": "numeric-float-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -128,28 +134,18 @@ } }, { - "flag": "float-5-as-integer", + "flag": "numeric-float-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-5-as-float", + "flag": "numeric-float-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -160,7 +156,29 @@ } }, { - "flag": "float--5-as-integer", + "flag": "numeric-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -169,30 +187,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--5-as-float", + "flag": "numeric-float-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float-1.25-as-integer", + "flag": "numeric-float-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -201,30 +227,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float-1.25-as-float", + "flag": "numeric-float-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": 1.25, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float--1.25-as-integer", + "flag": "numeric-float-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -233,23 +267,31 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--1.25-as-float", + "flag": "numeric-float-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -258,31 +300,69 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], - "evaluationEvents": [ + "exposures": [ { - "_count": 10, - "_skip": [ + "_count": 9, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + }, { "platform": "web", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." }, { "platform": "react-native", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." + } + ] + } + ], + "evaluationEvents": [ + { + "_count": 12, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." } ] }, { - "_count": 5, + "_count": 9, "_include": [ { "platform": "web", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." }, { "platform": "react-native", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-integer.json b/precomputed-assignments/cases/evaluator/numeric-integer.json index 381cfa53..44c71291 100644 --- a/precomputed-assignments/cases/evaluator/numeric-integer.json +++ b/precomputed-assignments/cases/evaluator/numeric-integer.json @@ -1,6 +1,6 @@ { "name": "numeric-integer", - "description": "numeric integer.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,53 +10,101 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "integer-0-as-integer": { + "numeric-integer-0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer-0-as-float": { + "numeric-integer-1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer-5-as-integer": { + "numeric-integer-2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer-5-as-float": { + "numeric-integer-3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer--5-as-integer": { + "numeric-integer-4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer--5-as-float": { + "numeric-integer-5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "numeric-integer-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } } } @@ -64,7 +112,7 @@ }, "evaluations": [ { - "flag": "integer-0-as-integer", + "flag": "numeric-integer-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -75,118 +123,246 @@ } }, { - "flag": "integer-0-as-float", + "flag": "numeric-integer-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -99.5, + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-3", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer-5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "numeric-integer-7", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": 5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer-5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "numeric-integer-8", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer--5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "numeric-integer-9", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer--5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "numeric-integer-10", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "flag": "numeric-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } } ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], - "evaluationEvents": [ + "exposures": [ { - "_count": 6, - "_skip": [ + "_count": 9, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + }, { "platform": "web", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." }, { "platform": "react-native", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." + } + ] + } + ], + "evaluationEvents": [ + { + "_count": 12, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." } ] }, { - "_count": 3, + "_count": 9, "_include": [ { "platform": "web", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." }, { "platform": "react-native", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-number.json b/precomputed-assignments/cases/evaluator/numeric-number.json index 946151c6..8839e781 100644 --- a/precomputed-assignments/cases/evaluator/numeric-number.json +++ b/precomputed-assignments/cases/evaluator/numeric-number.json @@ -1,6 +1,6 @@ { "name": "numeric-number", - "description": "numeric number.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,85 +10,101 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "number-0-as-integer": { + "numeric-number-0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-0-as-float": { + "numeric-number-1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-5-as-integer": { + "numeric-number-2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-5-as-float": { + "numeric-number-3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--5-as-integer": { + "numeric-number-4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--5-as-float": { + "numeric-number-5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-1.25-as-integer": { + "numeric-number-6": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-1.25-as-float": { + "numeric-number-7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--1.25-as-integer": { + "numeric-number-8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--1.25-as-float": { + "numeric-number-9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "numeric-number-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } } } @@ -96,7 +112,7 @@ }, "evaluations": [ { - "flag": "number-0-as-integer", + "flag": "numeric-number-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -107,7 +123,7 @@ } }, { - "flag": "number-0-as-float", + "flag": "numeric-number-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -118,7 +134,7 @@ } }, { - "flag": "number-5-as-integer", + "flag": "numeric-number-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -129,7 +145,7 @@ } }, { - "flag": "number-5-as-float", + "flag": "numeric-number-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -140,7 +156,7 @@ } }, { - "flag": "number--5-as-integer", + "flag": "numeric-number-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -151,7 +167,7 @@ } }, { - "flag": "number--5-as-float", + "flag": "numeric-number-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -162,7 +178,7 @@ } }, { - "flag": "number-1.25-as-integer", + "flag": "numeric-number-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -171,19 +187,27 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number-1.25-as-float", + "flag": "numeric-number-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -194,7 +218,7 @@ } }, { - "flag": "number--1.25-as-integer", + "flag": "numeric-number-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -203,23 +227,71 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number--1.25-as-float", + "flag": "numeric-number-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-number-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -228,31 +300,69 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], - "evaluationEvents": [ + "exposures": [ { - "_count": 10, - "_skip": [ + "_count": 9, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." + }, { "platform": "web", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." }, { "platform": "react-native", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." + } + ] + } + ], + "evaluationEvents": [ + { + "_count": 12, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "unity", + "reason": "Applicable evaluations for this API." } ] }, { - "_count": 8, + "_count": 9, "_include": [ { "platform": "web", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." }, { "platform": "react-native", - "reason": "Single Number API." + "reason": "Applicable evaluations for this API." } ] } diff --git a/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json b/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json index 2fd77213..801ecd1a 100644 --- a/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json +++ b/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json @@ -1,6 +1,6 @@ { "name": "type-mismatch-all-types", - "description": "Wrong typed getters return the caller default with TYPE_MISMATCH. APIs with one Number getter do not implement integer-versus-float mismatch.", + "description": "Wrong nonnumeric getters return TYPE_MISMATCH; an exact numeric conversion succeeds.", "context": { "targetingKey": "mismatch-user", "attributes": {} @@ -84,21 +84,11 @@ "variationType": "float", "defaultValue": 0.0, "expectedResult": { - "value": 0.0, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter." - } - ] + "value": 7, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "var-int" + } }, { "flag": "fixture.mismatch.float-as-bool", @@ -125,19 +115,14 @@ ], "expectations": { "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], "evaluationEvents": [ { - "_count": 5, - "_skip": [ - { - "platform": "web", - "reason": "Single Number API." - }, - { - "platform": "react-native", - "reason": "Single Number API." - } - ] + "_count": 5 } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json index 6547f39a..8c45fc5e 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-float", - "description": "numeric float.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,119 +10,125 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "4fc70afd0edbe9f6c3605f50b73892f4167f395a93aa5c2e91c5380818270237": { + "43dcecc88807de972a354bd0b96946419ccf086a3cb1abcaef10a796129ffb7e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "01c7520392b183ba08426484c8981ef7b9574d8d5e1f0b5b0afc04945b1fdc65": { + "ad5896fa25ecdb06cc985d99e469f2b867419c296d99142ba53b7a4ab11d0167": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "30a0b6a28e18bbdd712d677df8b0a0012086219260528d4967214fa45f36f90b": { + "5f920ae1126d6bf5dd3c7cde59b1604dfbae53d4ad4686a548871a4baec00f22": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "b432d3d8d83d0f10acfb8d8099c7dd1238b96b45e12f2407ae6bfd938ad92354": { + "a710178c1d9213803a5ff5f56146c93908327ac97407d76c6139dfdcd9a4012f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "26a957788e3e02fda8ace878f452615d85c2d80a539a19522b45c9fac903b905": { + "b81af3ac1aba6361e1eb0951f2e1babd617d2218827ce4a14f2c8a5f8005b885": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "fd7b22e26bc6227562127dc3945e30e159e1b522ba003b52706d5b33c8db0519": { + "de8b6921dbff87fdf7182b8a948b9644aa4db01a9dbbda9749a0774f189746cc": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "93cafc55f2224734ccd9a3a0030a8bd007dcded0f48320a16d2044744cc6a4f6": { + "9a8d577fc6f24f49f804fa068b9a7170c9328c0def19a2b2312c9f59b5dfab02": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "5ad25a909e7b8ef40cf13c71a7ec8f2ab4bb8a3ae4a5439029357f9982508bf8": { + "82077f26295c6ead94d7a7a7554ed4e143a4af711f8deb94b8a8a711d032c4f2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "4d677ea458fe5ed9851dbdba4f92981a432b5b9b467445032483b9fa2d2f7368": { + "8bf2c56f7653627e757f8263815d7408dbcbb6fb86d71d55402e9800f185ed22": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "eb98f328fb6978a74722d54badcaf0335bc7433ca86e205ecca0be0ac86012c8": { + "cef1691308f6e520bbdbc8575683d17bc814f52af8ef42522d6a5bf0aee7e861": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "8b79dc5672bf5c07c2a64b68cd885ba18db98b52639d349283ac6888477326a6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c0b84d2e8690addb339fd433c9cc449d6da6632ea8398d47e9bfebde442247a0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } }, "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "000102030405060708090a0b0c0d0e0f" + "salt": "00112233445566778899aabbccddeeff" } } } }, "evaluations": [ { - "flag": "float-0-as-integer", + "flag": "obfuscation-numeric-float-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-0-as-float", + "flag": "obfuscation-numeric-float-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -133,28 +139,18 @@ } }, { - "flag": "float-5-as-integer", + "flag": "obfuscation-numeric-float-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-5-as-float", + "flag": "obfuscation-numeric-float-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -165,7 +161,29 @@ } }, { - "flag": "float--5-as-integer", + "flag": "obfuscation-numeric-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-float-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -174,30 +192,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--5-as-float", + "flag": "obfuscation-numeric-float-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float-1.25-as-integer", + "flag": "obfuscation-numeric-float-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -206,30 +232,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float-1.25-as-float", + "flag": "obfuscation-numeric-float-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": 1.25, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float--1.25-as-integer", + "flag": "obfuscation-numeric-float-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -238,23 +272,31 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--1.25-as-float", + "flag": "obfuscation-numeric-float-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -263,31 +305,40 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], - "evaluationEvents": [ + "exposures": [ { - "_count": 10, - "_skip": [ + "_count": 9, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, { - "platform": "web", - "reason": "Single Number API." + "platform": "android", + "reason": "Applicable evaluations for this API." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "flutter", + "reason": "Applicable evaluations for this API." } ] - }, + } + ], + "evaluationEvents": [ { - "_count": 5, + "_count": 12, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "flutter", + "reason": "Applicable evaluations for this API." } ] } @@ -296,15 +347,15 @@ "_include": [ { "platform": "ios", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "android", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "flutter", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json index 7380abfe..dd60779a 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-integer", - "description": "numeric integer.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,66 +10,114 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "3a56e171b0efb081585be54227ac1792ebae79900fb0e0ee2c3850ebc7dd7fd0": { + "72101f81c2d474893bb90b2e9fbece5750b83659171b75d423eba3c8caa09af8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "3df507231bc602ed78f6e01db4497a88b33c086a455deba043f35dde999c0b7d": { + "158853f6fec44bdd14c7dcf732538d1932393333da0d38e5b1cf389febab173a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "58e7c159ddb85fe9250395e7797d80ecb7dca88e1a22767261a9482611baa358": { + "5e716e405677ef15a943d1e3ec8577a5436ad2b08b619801fa115e3f6056e7e1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "a4ef458efa0a3876a8509351d70fa007a68460b21c64cc64680c1099ec450dab": { + "55aa24315699bb2e6b22e1e60dbc59588628d4987393d1709e63283f740da782": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "01c7f586a7f7bd98d4cbc75760f506b6ff8a9ba7195e88f975a708cc7b6ebb30": { + "11dbbd2cc435f0d8521f492798f5cdeda39b44612afb5b2e8dbbca244eac8afb": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "14a559f9d240120a5038b7f3f6529a0cba11d3cbc84409c84678290f4116b809": { + "5f600bc0a426abe99c1d0f650b9b8eb243179105c4a73a59f696b99967088fcd": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "422fd4f38b4009a3443ac2334e4687d21e2eb02d7614a3eec83c7630585d7f4e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "77e8ec1779ffe7a8edd77b8c84358cae99993a9898b4c0cadf2336a88ae99ccd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6779f3038ba17b00bc22bd42f0fb7078c7a3cf7843adc7884e269ad0b33a0180": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7224ebe3cb4e571ec923f2624b68bd3e183c199258e5523e6456b62e9254f8e5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9483e1046878fedc712a9345a3e8406df5b24d42f7e0d4500ff8cc9fb1c9a161": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e3e9c957b3953d38da8ada7fadcb6bea2ce78691d7cc34dbc2c75297ee4c5a95": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } }, "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "000102030405060708090a0b0c0d0e0f" + "salt": "00112233445566778899aabbccddeeff" } } } }, "evaluations": [ { - "flag": "integer-0-as-integer", + "flag": "obfuscation-numeric-integer-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -80,118 +128,217 @@ } }, { - "flag": "integer-0-as-float", + "flag": "obfuscation-numeric-integer-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-3", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -99.5, + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer-5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "obfuscation-numeric-integer-7", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": 5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer-5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "obfuscation-numeric-integer-8", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer--5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "obfuscation-numeric-integer-9", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer--5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "obfuscation-numeric-integer-10", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "flag": "obfuscation-numeric-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } } ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], - "evaluationEvents": [ + "exposures": [ { - "_count": 6, - "_skip": [ + "_count": 9, + "_include": [ + { + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, { - "platform": "web", - "reason": "Single Number API." + "platform": "android", + "reason": "Applicable evaluations for this API." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "flutter", + "reason": "Applicable evaluations for this API." } ] - }, + } + ], + "evaluationEvents": [ { - "_count": 3, + "_count": 12, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "flutter", + "reason": "Applicable evaluations for this API." } ] } @@ -200,15 +347,15 @@ "_include": [ { "platform": "ios", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "android", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "flutter", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json index 9e54ced9..f2b1fd91 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-number", - "description": "numeric number.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,98 +10,114 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "085fa5b25a3d42952668a59d052deb6ee5bc2418a352b9887556fe862ef78ca8": { + "5d1d3c167fda7f3ecadb68da40228431ed0b74ec08597fc57398fbf7f253f0d6": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "66aeb85f02e96149bd485b28375161c4bc154e748545593a40da549d53f479c2": { + "4dcb0659aaeb33eb329143c5ed994c52800521968edafd58c95c50377873ccc4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "eef7048e9d655ec6f29019eec43986e1d4ba6ff42523ecd8f46b6ac0ae883050": { + "8023572892cfed9fb5ee411c451eab7480329f32331f3845e76234275bede04d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "a46a4fdb29e1de92315dc1ee481f59a33455841d317e38191f13cdcdd9fd2eb0": { + "d15970ae43ef67b0a9844f04cd8680567400e12340d545aa608a0cc9b89bed95": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "5f25ccca362e35bf27475aa056da166b4e7ecf6ed66b65bc25f9d573717d2628": { + "4373eda204ffa94cc5c15d29bd5a33b828033f4d2382f708801a7ea5c298b286": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "914ae5836aae14f5bd8348d1669e008e400c43887d08cd6dc67b028d914e8c6a": { + "5a95ab1a979318a8d113fcdb44a9a5262f0f99a6fb58e0f782b1ac5dbc88f1ef": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "3cecd82b1b8e3c49bc3fe1886fdd0a57de07ac68a437f59bf7c6c6317d83b03b": { + "dceef8e665186596db59e1c8bf2887adf20969530b48091e1bcf3517bf804a7b": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "5a539f4ac96a34becffeed98230a919b3082e085bb79deed40d88600d5500636": { + "6b8f85c278e445167be847d3a94a91c1865221df7cc63dc8f31aacbb9454e430": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "ec97872ee38dc677992d1df24af2093298d35a35d246ea3dd39df7e33279b358": { + "81da1069b7cb802d039c7cd27d4c0f4db2515a5c64dd411a522d6f300592c31c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "4b33526589906dcabeefa0e880ad6697d4e6673af26187c67947124777a67bbc": { + "0a2bcee9cec623b02ca219d7b3f15415767bb630f6bcf9af75fda36c2a689f64": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "be4ecda1ccdb165dc68fcffc30f021c7e83a88b8a2283784498ac761385f26d0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c4f3ba7ee684fddd93335901ae9b0af3959704d06b5a028449b08854d5384e3d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } }, "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "000102030405060708090a0b0c0d0e0f" + "salt": "00112233445566778899aabbccddeeff" } } } }, "evaluations": [ { - "flag": "number-0-as-integer", + "flag": "obfuscation-numeric-number-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -112,7 +128,7 @@ } }, { - "flag": "number-0-as-float", + "flag": "obfuscation-numeric-number-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -123,7 +139,7 @@ } }, { - "flag": "number-5-as-integer", + "flag": "obfuscation-numeric-number-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -134,7 +150,7 @@ } }, { - "flag": "number-5-as-float", + "flag": "obfuscation-numeric-number-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -145,7 +161,7 @@ } }, { - "flag": "number--5-as-integer", + "flag": "obfuscation-numeric-number-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -156,7 +172,7 @@ } }, { - "flag": "number--5-as-float", + "flag": "obfuscation-numeric-number-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -167,7 +183,7 @@ } }, { - "flag": "number-1.25-as-integer", + "flag": "obfuscation-numeric-number-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -176,19 +192,27 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number-1.25-as-float", + "flag": "obfuscation-numeric-number-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -199,7 +223,7 @@ } }, { - "flag": "number--1.25-as-integer", + "flag": "obfuscation-numeric-number-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -208,23 +232,71 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number--1.25-as-float", + "flag": "obfuscation-numeric-number-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-number-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "obfuscation-numeric-number-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -233,31 +305,40 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], - "evaluationEvents": [ + "exposures": [ { - "_count": 10, - "_skip": [ + "_count": 9, + "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "Applicable evaluations for this API." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "android", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "flutter", + "reason": "Applicable evaluations for this API." } ] - }, + } + ], + "evaluationEvents": [ { - "_count": 8, + "_count": 12, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "Applicable evaluations for this API." + }, + { + "platform": "android", + "reason": "Applicable evaluations for this API." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "flutter", + "reason": "Applicable evaluations for this API." } ] } @@ -266,15 +347,15 @@ "_include": [ { "platform": "ios", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "android", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "flutter", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." } ] } From 9d235a1f25d6b9425a575cf8413b6664ea576b7e Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 16:16:20 -0500 Subject: [PATCH 07/12] FFL-3568: Assert numeric event identity and encoded boundaries --- .../evaluator/numeric-boundaries-float.json | 494 +++++++- .../evaluator/numeric-boundaries-integer.json | 494 +++++++- .../evaluator/numeric-boundaries-number.json | 494 +++++++- .../cases/evaluator/numeric-float.json | 199 ++- .../cases/evaluator/numeric-integer.json | 199 ++- .../cases/evaluator/numeric-number.json | 199 ++- .../obfuscation-numeric-boundaries-float.json | 1111 +++++++++++++++++ ...bfuscation-numeric-boundaries-integer.json | 1111 +++++++++++++++++ ...obfuscation-numeric-boundaries-number.json | 1111 +++++++++++++++++ .../obfuscation-numeric-float.json | 196 ++- .../obfuscation-numeric-integer.json | 196 ++- .../obfuscation-numeric-number.json | 196 ++- 12 files changed, 5832 insertions(+), 168 deletions(-) create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json create mode 100644 precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json index fccee380..a17705d5 100644 --- a/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json @@ -568,58 +568,524 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 15, + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-1" + }, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "_count": 8, + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-3" + }, "_include": [ { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + } } ], "evaluationEvents": [ { - "_count": 18, + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-1" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-3" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-4" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + }, + "evaluation_count": 1, + "runtime_default_used": false }, { - "_count": 13, + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-7" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-9" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-10" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-12" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-14" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-15" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-16" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-17" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-18" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-19" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-20" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] }, diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json index 1f803e58..841d57fc 100644 --- a/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json @@ -568,58 +568,524 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 15, + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-1" + }, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "_count": 8, + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-3" + }, "_include": [ { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + } } ], "evaluationEvents": [ { - "_count": 18, + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-1" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-3" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-4" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + }, + "evaluation_count": 1, + "runtime_default_used": false }, { - "_count": 13, + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-7" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-9" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-10" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-12" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-14" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-15" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-16" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-17" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-18" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-19" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-20" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] }, diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json index 4fad273a..79247edc 100644 --- a/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json @@ -568,58 +568,524 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 15, + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-1" + }, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "_count": 8, + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-3" + }, "_include": [ { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + } } ], "evaluationEvents": [ { - "_count": 18, + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-1" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-3" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-4" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + }, + "evaluation_count": 1, + "runtime_default_used": false }, { - "_count": 13, + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-7" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-9" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-10" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-12" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-14" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-15" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-16" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-17" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-18" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-19" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-20" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] }, diff --git a/precomputed-assignments/cases/evaluator/numeric-float.json b/precomputed-assignments/cases/evaluator/numeric-float.json index e6868d0e..be81c766 100644 --- a/precomputed-assignments/cases/evaluator/numeric-float.json +++ b/precomputed-assignments/cases/evaluator/numeric-float.json @@ -302,69 +302,210 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 9, + "_count": 1, + "flag": { + "key": "numeric-float-0" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-1" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-3" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-4" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-7" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-9" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-11" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-float-0" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-1" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-3" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-4" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." - }, - { - "platform": "web", - "reason": "Applicable evaluations for this API." - }, - { - "platform": "react-native", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] - } - ], - "evaluationEvents": [ + ], + "runtime_default_used": true + }, { - "_count": 12, + "_count": 1, + "flag": { + "key": "numeric-float-7" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-8" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true }, { - "_count": 9, + "_count": 1, + "flag": { + "key": "numeric-float-9" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Applicable evaluations for this API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Applicable evaluations for this API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-11" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-integer.json b/precomputed-assignments/cases/evaluator/numeric-integer.json index 44c71291..ce2063b5 100644 --- a/precomputed-assignments/cases/evaluator/numeric-integer.json +++ b/precomputed-assignments/cases/evaluator/numeric-integer.json @@ -302,69 +302,210 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 9, + "_count": 1, + "flag": { + "key": "numeric-integer-0" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-1" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-3" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-4" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-7" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-9" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-11" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-integer-0" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-1" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-3" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-4" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." - }, - { - "platform": "web", - "reason": "Applicable evaluations for this API." - }, - { - "platform": "react-native", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] - } - ], - "evaluationEvents": [ + ], + "runtime_default_used": true + }, { - "_count": 12, + "_count": 1, + "flag": { + "key": "numeric-integer-7" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-8" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true }, { - "_count": 9, + "_count": 1, + "flag": { + "key": "numeric-integer-9" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Applicable evaluations for this API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Applicable evaluations for this API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-11" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-number.json b/precomputed-assignments/cases/evaluator/numeric-number.json index 8839e781..11ebe3b0 100644 --- a/precomputed-assignments/cases/evaluator/numeric-number.json +++ b/precomputed-assignments/cases/evaluator/numeric-number.json @@ -302,69 +302,210 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 9, + "_count": 1, + "flag": { + "key": "numeric-number-0" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-1" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-3" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-4" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-7" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-9" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-11" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-number-0" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-1" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-3" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-4" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." - }, - { - "platform": "web", - "reason": "Applicable evaluations for this API." - }, - { - "platform": "react-native", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] - } - ], - "evaluationEvents": [ + ], + "runtime_default_used": true + }, { - "_count": 12, + "_count": 1, + "flag": { + "key": "numeric-number-7" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-8" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "unity", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true }, { - "_count": 9, + "_count": 1, + "flag": { + "key": "numeric-number-9" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Applicable evaluations for this API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Applicable evaluations for this API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-11" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json new file mode 100644 index 00000000..9aa406cd --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json @@ -0,0 +1,1111 @@ +{ + "name": "obfuscation-numeric-boundaries-float", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "2aec5b3d4477769702ec52c42349be07a31c413ebffb69196b762523adf205ab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ccd840e29b0846bbc8ace7da7bc20c300b2a14e1f5f68961f2fb76e69fb0ba04": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ebe608c5e7ab0bf15e0c6585810759616ab5881130d7ddb956ec8749c43a0939": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1f2de3a9ac37932ca8fbeaafe88046f8f7a49075c87eec5b805a0967706685a4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "431adeb8d8d06d55e943812fd5cf8bf6095858eb898fd3133999c3d163607b3d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2c7494350d45235245c36589fe36990e14f5e3eca4522e7c93b05ec4e4e3de7c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "19b8019e6dc73fdabe4884fd109c0c0865eb682b5a6e9b2c098d8698cb8a25bf": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "05dce5b4f6ec9f670d767a9e2f3095c91dc5f947c0cf65f26ffe0d34d169c27a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b31d785097e2b21ff4203b7f42d9601f0fe117e04ae52fd41c38ec226ee7c9dd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2561c2c5e59e5ee958e65c5ffc5afdc82deab8caacc24b7fd955426d38f3f10a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a4378fa74480657ce942442b4755051dfe8254b3a869ef1d09e73b70905931ba": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4c973da42e257593047a0e547423acae08bcb5d6529670d6917dc653d559878d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6291d2c93c31947490a82ac572b34ff0a2cfc6d1bf63ee929bd4c427e4c14e46": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ed9c529fd1165fc1f8560f540dd0741dd240473987473db250c027486ca54481": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "87217664c8cdba2054ae7ba5e0513f1fe0932a93deb52ee46c8c4856db50bfaf": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "435af2981e1d1cf3e2e762b9fbb1b99f4850d1b0d4bed9de0f44f0418b223506": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "608602ea98e8c47ffb9f86e51e8093daa81ff902841d774092b32df7f92421e2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ae43ae724ec81be659b9cbd89315102b127b1b9e24e26def87496eb4a8aa38ab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2358b34f81306a3acd892893294abea0c33721a65b06e355cda36c8e54739be5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9fc154f59fba732da1a2be979ec48044cd88f185fd90b9cfc306e53f3f6d6a88": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "350edcb8f9921229dea893c8a3948457281d0ef88537c3c8905a4dd924165228": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3ff68a68d0c6c04348089c33052aad256a51086ae365496d7ecf89a55c9a4d70": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00112233445566778899aabbccddeeff" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-float-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-1" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-3" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-1" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-3" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-4" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-6" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-7" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-9" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-10" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-12" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-14" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-15" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-16" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-17" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-18" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-19" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-20" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + }, + "evaluation_count": 1, + "runtime_default_used": false + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "android", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "flutter", + "reason": "Requires native numeric precision or supported obfuscation." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json new file mode 100644 index 00000000..19858f93 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json @@ -0,0 +1,1111 @@ +{ + "name": "obfuscation-numeric-boundaries-integer", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "7c5c55bbc1d947ebd29d4d22bbb8e854fb84a3168ec16c9b88ff1eb8048f1259": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1db93bef085224805fc0b94e254d8efea2ad144e255512c7822ba1941efd37ca": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c65f42d21a69e9e63e768bb7c353ccc659d8fa7fda19d552c46571fe747939a1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "57ea520510c0e0ffdf6c016032a1ad8fd7e930feddb56e512c5ba5e176bb747e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3bae488ac7f7c5e64e8d80c5e5d3a01fede98ee9dc1dbfaaa1602bdb834e3199": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "d7e31a5af23eaed253fbab5d5ba775a49cc869265a762861623b9850ca45200e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e214fbcdc5fa2e6b53e80a737cf3b01bf720e1da6bdc8d1a8126c82fae131cc5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f87c7ffcbfc101c063bc2f6533d4a73054a4c3e0cc59b865b6d9a738cded6776": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "24e2195a49630a3b88efcb5227d7044651ffa1e483006af5ef3e41db492ca96a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "45316309ea93c9d4d63ebd704488bd9b862cb9d30303b54ccbc668a45f1a9cfd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "107e31005fc784e93f438721722b248ed66b8ed91d13eefca267786102fe5a81": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ca603c439f5fa9a01c76f4febf58bf0b1034e100dd37fd1731c05f667409846e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bf801a713f62854431659efc54a0cb312dd601a8459d4390b13840fd7f414a02": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6c6bc2ace96267f8a839f207457b3d12b93f8211e7ce9a2f4ef9b2ddfc70412b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "cd01bfe31bd7117a646dddc5be9732400e8ee0076cafd79c189115b8b3081950": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fe4005d2eb3448257136861a88114cadcc85f06cb2ede14a36b581d4900e4a5e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fb7e52e4c272d1e4e9602339c4f6a7094257194ea28d274314b8ca0a4aec639b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "de7cefc4a649b9c39f7fe31b49d5fa5f8ac6b792e2227717e0130dd127bbfd70": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e084557c149a57fcf7e04041ad080c04586380baa9093a16dfee198a1ad51989": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f163106c94e153c44a0f9c882c90c6c5771e7df230fb847e3a9426bf18a8f312": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "40aa0011039c7b0946bb1264199ba576db480b932c341fa5a224340056ec0976": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "79ccc13c690d354291ea1fbee6c0b103d273130dd90bb6f3efe51840d1c562ae": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00112233445566778899aabbccddeeff" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-integer-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-1" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-3" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-1" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-3" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-4" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-6" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-7" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-9" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-10" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-12" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-14" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-15" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-16" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-17" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-18" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-19" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-20" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + }, + "evaluation_count": 1, + "runtime_default_used": false + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "android", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "flutter", + "reason": "Requires native numeric precision or supported obfuscation." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json new file mode 100644 index 00000000..3e8c3877 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json @@ -0,0 +1,1111 @@ +{ + "name": "obfuscation-numeric-boundaries-number", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "b4ba8411e13132261ecb4f57a72e7ca330d2618c536f5546590abb0cc25b9c47": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "82796957263227e5d42ba913e864aca7aa0f6c06cf30ef1bd6a52cf771a5680e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9889dbdc0d0d8741c09f2d9e1f6b764e37995c66e76452502ff7a73398dc8fdb": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "20c38d5ef57bd5c41cc52d311ab1147afabfcdbb2abbdaf47f9c1d5f71736248": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a989158eea675eb49b1f62e15c2205b1a1168d9ed769e90e16ba8f43e65963c2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fd02ac0da0683586431856ba869c1ff07669b94244e6c8a58c7e1ae8dcb3a04d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "0c4bdf647950f33d16dace93f197e1891b73d767a5bc3be0cf1e51a115775f63": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bb03a68efa88e918862e8a481ee25893c059765384e7a0835b022f742ecffc05": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "596ef4687c078263b87dd17061e7471add9255b49814d72150bac75dce35787d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "920cb9612332743d61020ef481f4990bf0826040b3d087212a891115e9f30f22": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "62e88abd1cfc891df7f5703503077e82fb99db1e9be9b91d9a6b50333b4d1110": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a99b58377ef7b9e52d6e1c3befd549d61e1ca234eca3cd8d4720c16928d9a7a0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ff66a9c09d22765cf9397d3e156bce434a683325f21ab6df1c9b7359056d676a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9b15e9496c8eb9e19f2e8afc18ac298593ea8d45370e1fceb0f98b0f47e28fdb": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e0d4ba65f888bbf2b204e1ed4cc02d23b0e0c3f8a0f4f22b51501c7532d4781f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9b5089501d028a056aae6a5de88452f98f79340ba32f765a3950c6d8c0a82a9d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1706e7b05d435e118cb4dee1540e2109fd7a7b1b19b08fceec0cfebc8fc0abc1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3769e087d36053e2971961b47640ab9b7397f02b1eddda5d4e90819ef1b1f5bb": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e0cff618c57a7aca3a66fbbb36b72aa53a4d4b7a08a5c67a0e25ffd564cea395": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4d204756440205698d05663c5a511d991d8e7ad64f54b1a257034b8e2c52f80f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4c741509a1ac866498f61619846c2cdec7fd84bbc362ee6ff3f8d563f2156fc8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c9bbbec289c2c83e830e9bffd2ff577daf18bb71371cff7c08a5372ed860d957": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00112233445566778899aabbccddeeff" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-number-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-1" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-3" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-1" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-3" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-4" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-6" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-7" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-9" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-10" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-12" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-14" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-15" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-16" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-17" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-18" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-19" + }, + "evaluation_count": 1, + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-20" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + }, + "evaluation_count": 1, + "runtime_default_used": false + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "android", + "reason": "Requires native numeric precision or supported obfuscation." + }, + { + "platform": "flutter", + "reason": "Requires native numeric precision or supported obfuscation." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json index 8c45fc5e..fa6f4cf9 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json @@ -307,40 +307,210 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 9, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-0" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-1" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-2" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-3" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-4" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-5" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-7" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-9" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-11" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-0" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-1" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-3" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-4" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] - } - ], - "evaluationEvents": [ + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-7" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-8" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, { - "_count": 12, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-9" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-10" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-11" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json index dd60779a..140b04f6 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json @@ -307,40 +307,210 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 9, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-0" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-1" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-2" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-3" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-4" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-5" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-7" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-9" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-11" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-0" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-1" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-3" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-4" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] - } - ], - "evaluationEvents": [ + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-7" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-8" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, { - "_count": 12, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-9" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-10" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-11" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json index f2b1fd91..1e4cb869 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json @@ -307,40 +307,210 @@ "noUnmatchedEvents": true, "exposures": [ { - "_count": 9, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-0" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-1" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-2" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-3" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-4" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-5" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-7" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-9" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-11" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-0" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-1" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-2" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-3" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-4" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-5" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-6" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] - } - ], - "evaluationEvents": [ + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-7" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-8" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, { - "_count": 12, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-9" + }, + "evaluation_count": 1, + "runtime_default_used": false + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-10" + }, + "evaluation_count": 1, "_include": [ { "platform": "ios", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "android", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." }, { "platform": "flutter", - "reason": "Applicable evaluations for this API." + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-11" + }, + "evaluation_count": 1, + "runtime_default_used": false } ] }, From 0feda21aec6710a5243287fbc58a33ca2f658b16 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 16:16:41 -0500 Subject: [PATCH 08/12] FFL-3568: Allow omitted default marker on successful events --- .../evaluator/numeric-boundaries-float.json | 51 +++++++------------ .../evaluator/numeric-boundaries-integer.json | 51 +++++++------------ .../evaluator/numeric-boundaries-number.json | 51 +++++++------------ .../cases/evaluator/numeric-float.json | 27 ++++------ .../cases/evaluator/numeric-integer.json | 27 ++++------ .../cases/evaluator/numeric-number.json | 27 ++++------ .../obfuscation-numeric-boundaries-float.json | 51 +++++++------------ ...bfuscation-numeric-boundaries-integer.json | 51 +++++++------------ ...obfuscation-numeric-boundaries-number.json | 51 +++++++------------ .../obfuscation-numeric-float.json | 27 ++++------ .../obfuscation-numeric-integer.json | 27 ++++------ .../obfuscation-numeric-number.json | 27 ++++------ 12 files changed, 156 insertions(+), 312 deletions(-) diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json index a17705d5..c7c4335c 100644 --- a/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json @@ -796,8 +796,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -814,16 +813,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -840,8 +837,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -858,16 +854,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -902,16 +896,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-8" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -946,16 +938,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -972,16 +962,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-13" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -998,8 +986,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1024,8 +1011,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1050,8 +1036,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1076,16 +1061,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-21" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json index 841d57fc..4177018b 100644 --- a/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json @@ -796,8 +796,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -814,16 +813,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -840,8 +837,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -858,16 +854,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -902,16 +896,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-8" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -946,16 +938,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -972,16 +962,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-13" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -998,8 +986,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1024,8 +1011,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1050,8 +1036,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1076,16 +1061,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-21" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json index 79247edc..4be91412 100644 --- a/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json @@ -796,8 +796,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -814,16 +813,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -840,8 +837,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -858,16 +854,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -902,16 +896,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-8" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -946,16 +938,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -972,16 +962,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-13" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -998,8 +986,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1024,8 +1011,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1050,8 +1036,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1076,16 +1061,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-21" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/evaluator/numeric-float.json b/precomputed-assignments/cases/evaluator/numeric-float.json index be81c766..dd8b3cb8 100644 --- a/precomputed-assignments/cases/evaluator/numeric-float.json +++ b/precomputed-assignments/cases/evaluator/numeric-float.json @@ -362,48 +362,42 @@ "flag": { "key": "numeric-float-0" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-float-1" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-float-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-float-3" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-float-4" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-float-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -436,8 +430,7 @@ "flag": { "key": "numeric-float-7" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -470,8 +463,7 @@ "flag": { "key": "numeric-float-9" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -504,8 +496,7 @@ "flag": { "key": "numeric-float-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-integer.json b/precomputed-assignments/cases/evaluator/numeric-integer.json index ce2063b5..e1ce0740 100644 --- a/precomputed-assignments/cases/evaluator/numeric-integer.json +++ b/precomputed-assignments/cases/evaluator/numeric-integer.json @@ -362,48 +362,42 @@ "flag": { "key": "numeric-integer-0" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-integer-1" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-integer-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-integer-3" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-integer-4" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-integer-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -436,8 +430,7 @@ "flag": { "key": "numeric-integer-7" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -470,8 +463,7 @@ "flag": { "key": "numeric-integer-9" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -504,8 +496,7 @@ "flag": { "key": "numeric-integer-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] } diff --git a/precomputed-assignments/cases/evaluator/numeric-number.json b/precomputed-assignments/cases/evaluator/numeric-number.json index 11ebe3b0..c97208a8 100644 --- a/precomputed-assignments/cases/evaluator/numeric-number.json +++ b/precomputed-assignments/cases/evaluator/numeric-number.json @@ -362,48 +362,42 @@ "flag": { "key": "numeric-number-0" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-number-1" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-number-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-number-3" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-number-4" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "numeric-number-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -436,8 +430,7 @@ "flag": { "key": "numeric-number-7" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -470,8 +463,7 @@ "flag": { "key": "numeric-number-9" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -504,8 +496,7 @@ "flag": { "key": "numeric-number-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json index 9aa406cd..4ad1d694 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json @@ -801,8 +801,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -819,16 +818,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -845,8 +842,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -863,16 +859,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -907,16 +901,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-8" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -951,16 +943,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -977,16 +967,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-13" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -1003,8 +991,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1029,8 +1016,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1055,8 +1041,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1081,16 +1066,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-float-21" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json index 19858f93..9f6a4ef3 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json @@ -801,8 +801,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -819,16 +818,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -845,8 +842,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -863,16 +859,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -907,16 +901,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-8" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -951,16 +943,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -977,16 +967,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-13" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -1003,8 +991,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1029,8 +1016,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1055,8 +1041,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1081,16 +1066,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-integer-21" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json index 3e8c3877..96b428ad 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json @@ -801,8 +801,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -819,16 +818,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -845,8 +842,7 @@ "platform": "unity", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -863,16 +859,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -907,16 +901,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-8" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -951,16 +943,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -977,16 +967,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-13" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -1003,8 +991,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1029,8 +1016,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1055,8 +1041,7 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, @@ -1081,16 +1066,14 @@ "platform": "flutter", "reason": "This case requires the stated native numeric accessor range." } - ], - "runtime_default_used": false + ] }, { "_count": 1, "flag": { "key": "numeric-boundaries-number-21" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json index fa6f4cf9..fdbedade 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json @@ -367,48 +367,42 @@ "flag": { "key": "obfuscation-numeric-float-0" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-float-1" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-float-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-float-3" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-float-4" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-float-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -441,8 +435,7 @@ "flag": { "key": "obfuscation-numeric-float-7" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -475,8 +468,7 @@ "flag": { "key": "obfuscation-numeric-float-9" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -509,8 +501,7 @@ "flag": { "key": "obfuscation-numeric-float-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json index 140b04f6..4b638c49 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json @@ -367,48 +367,42 @@ "flag": { "key": "obfuscation-numeric-integer-0" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-integer-1" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-integer-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-integer-3" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-integer-4" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-integer-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -441,8 +435,7 @@ "flag": { "key": "obfuscation-numeric-integer-7" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -475,8 +468,7 @@ "flag": { "key": "obfuscation-numeric-integer-9" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -509,8 +501,7 @@ "flag": { "key": "obfuscation-numeric-integer-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json index 1e4cb869..92530649 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json +++ b/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json @@ -367,48 +367,42 @@ "flag": { "key": "obfuscation-numeric-number-0" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-number-1" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-number-2" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-number-3" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-number-4" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, "flag": { "key": "obfuscation-numeric-number-5" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -441,8 +435,7 @@ "flag": { "key": "obfuscation-numeric-number-7" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -475,8 +468,7 @@ "flag": { "key": "obfuscation-numeric-number-9" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 }, { "_count": 1, @@ -509,8 +501,7 @@ "flag": { "key": "obfuscation-numeric-number-11" }, - "evaluation_count": 1, - "runtime_default_used": false + "evaluation_count": 1 } ] }, From 01248630118659ac6d19c9ab5b0888f7efae5b49 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 16:25:12 -0500 Subject: [PATCH 09/12] FFL-3568: Pin checkout and satisfy fixture importer lint --- ci/sync-precomputed-fixtures.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 ci/sync-precomputed-fixtures.py diff --git a/ci/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py old mode 100644 new mode 100755 index d47e01c1..e6ecb047 --- a/ci/sync-precomputed-fixtures.py +++ b/ci/sync-precomputed-fixtures.py @@ -7,11 +7,11 @@ import hashlib import json import os -from pathlib import Path import re import subprocess import tempfile import uuid +from pathlib import Path REPOSITORY = 'https://github.com/DataDog/ffe-system-test-data.git' PREFIX = 'precomputed-assignments/' @@ -56,7 +56,7 @@ def snapshot(source, ref): if not isinstance(data, dict) or data.get('name') != Path(path).stem: raise ValueError('Invalid fixture identity: ' + path) if not isinstance(data.get('evaluations'), list): - raise ValueError('Missing evaluations: ' + path) + raise TypeError('Missing evaluations: ' + path) result['index.json'] = (json.dumps(cases, indent=2) + '\n').encode() result['SOURCE.json'] = (json.dumps({ 'repository': REPOSITORY, From 8dcc09a53dfb654cb0b2de8cda96919710f074e6 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 18:26:45 -0500 Subject: [PATCH 10/12] FFL-3568: Enforce generated obfuscation parity in CI --- .../validate-precomputed-fixtures.yml | 4 + AGENTS.md | 15 + ci/precomputed_obfuscation.py | 111 ++++ ci/test_precomputed_obfuscation.py | 115 ++++ precomputed-assignments/README.md | 34 ++ .../obfuscation-all-doLog-false.json | 106 ++++ .../obfuscation-duplicate-evaluation.json | 82 +++ .../obfuscation-serial-id-on-exposure.json | 142 +++++ .../obfuscation-all-types-success.json | 161 ++++++ ...fuscation-defaults-and-emission-gates.json | 130 +++++ .../obfuscation-empty-flags-response.json | 83 +++ .../evaluator/obfuscation-falsy-values.json | 148 ++++++ ...scation-integer-precision-native-only.json | 66 +++ .../evaluator/obfuscation-multi-flag-set.json | 490 ++++++++++++++++++ .../obfuscation-multiple-reasons.json | 110 ++++ .../obfuscation-numeric-boundaries-float.json | 54 +- ...bfuscation-numeric-boundaries-integer.json | 54 +- ...obfuscation-numeric-boundaries-number.json | 54 +- .../evaluator}/obfuscation-numeric-float.json | 100 ++-- .../obfuscation-numeric-integer.json | 100 ++-- .../obfuscation-numeric-number.json | 100 ++-- .../obfuscation-object-nested-deep.json | 106 ++++ ...bfuscation-special-characters-in-keys.json | 127 +++++ .../obfuscation-type-mismatch-all-types.json | 148 ++++++ .../obfuscation-exceptions.json | 1 + 25 files changed, 2410 insertions(+), 231 deletions(-) create mode 100644 ci/precomputed_obfuscation.py create mode 100644 ci/test_precomputed_obfuscation.py create mode 100644 precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json rename precomputed-assignments/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-boundaries-float.json (93%) rename precomputed-assignments/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-boundaries-integer.json (93%) rename precomputed-assignments/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-boundaries-number.json (93%) rename precomputed-assignments/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-float.json (80%) rename precomputed-assignments/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-integer.json (79%) rename precomputed-assignments/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-number.json (79%) create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json create mode 100644 precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json create mode 100644 precomputed-assignments/obfuscation-exceptions.json diff --git a/.github/workflows/validate-precomputed-fixtures.yml b/.github/workflows/validate-precomputed-fixtures.yml index b4f7cc8c..d8b68825 100644 --- a/.github/workflows/validate-precomputed-fixtures.yml +++ b/.github/workflows/validate-precomputed-fixtures.yml @@ -6,6 +6,7 @@ on: - .github/workflows/validate-precomputed-fixtures.yml - ci/validate-precomputed-fixtures.py - ci/test_precomputed*.py + - ci/precomputed_obfuscation.py - ci/sync-precomputed-fixtures.py - schemas/precomputed-assignment.schema.json - precomputed-assignments/** @@ -35,5 +36,8 @@ jobs: - name: Validate precomputed fixtures run: python3 ci/validate-precomputed-fixtures.py + - name: Verify plain and obfuscated fixture parity + run: python3 ci/precomputed_obfuscation.py --check + - name: Test fixture validation and snapshot import run: python3 -m unittest discover -s ci -p 'test_precomputed*.py' diff --git a/AGENTS.md b/AGENTS.md index af01e6ed..9ea8353c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -207,3 +207,18 @@ Example — web aggregates 2 evaluations into 1 event: {"_count": 1, "_include": [{"platform": "web"}], "evaluation_count": 2} ] ``` + +## Obfuscated companions + +Generate a companion for each plain precomputed fixture: + +```bash +python3 ci/precomputed_obfuscation.py --write +python3 ci/precomputed_obfuscation.py --check +``` + +Do not edit `precomputed-assignments/cases/obfuscation/generated/` manually. +Keep rejection fixtures and fixed digest vectors outside that generated folder. +CI checks companion content, not just file existence. A new case is discovered +without a filename list. Read [the coverage policy](precomputed-assignments/README.md#plain-and-obfuscated-coverage) +for the explicit exception format and platform applicability rules. diff --git a/ci/precomputed_obfuscation.py b/ci/precomputed_obfuscation.py new file mode 100644 index 00000000..204a39bb --- /dev/null +++ b/ci/precomputed_obfuscation.py @@ -0,0 +1,111 @@ +"""Generate and verify obfuscated companions of plain precomputed fixtures.""" + +import argparse +import copy +import hashlib +import json +from pathlib import Path + +SUPPORTED_PLATFORMS = ("ios", "android", "flutter") +SALT = "000102030405060708090a0b0c0d0e0f" +DOMAIN = b"datadog.feature-flags.flag-key.v1\0" + + +def encode_key(key): + return hashlib.sha256(DOMAIN + bytes.fromhex(SALT) + key.encode("utf-8")).hexdigest() + + +def companion(source): + result = copy.deepcopy(source) + attributes = result["response"]["data"]["attributes"] + if "obfuscation" in attributes or attributes.get("obfuscated", False) is not False: + raise ValueError("Source must contain a plain response") + if not isinstance(attributes.get("flags"), dict): + raise ValueError("Source must contain a flag map; document an exception if inapplicable") + included = {entry["platform"] for entry in source.get("_include", [])} + skipped = {entry["platform"] for entry in source.get("_skip", [])} + platforms = [p for p in SUPPORTED_PLATFORMS + if ("_include" not in source or p in included) and p not in skipped] + if not platforms: + raise ValueError("No supported obfuscation consumer; document an exception") + result["name"] = "obfuscation-" + source["name"] + result["description"] = "Generated obfuscated companion: " + source["description"] + result.pop("_skip", None) + result["_include"] = [ + {"platform": p, "reason": "Supports obfuscation and the source fixture."} + for p in platforms + ] + attributes["flags"] = {encode_key(key): value for key, value in attributes["flags"].items()} + attributes["obfuscated"] = True + attributes["obfuscation"] = {"scheme": "flag-key-sha256-v1", "salt": SALT} + # Public lookup keys, results, events, and evaluation filters remain identical. + return result + + +def expected_companions(cases, exceptions_path): + sources = {p.relative_to(cases).as_posix(): p for p in sorted(cases.rglob("*.json")) + if p.relative_to(cases).parts[0] != "obfuscation"} + if not sources: + raise ValueError("No plain fixtures found") + exceptions = json.loads(exceptions_path.read_text()) + if not isinstance(exceptions, dict): + raise ValueError("Obfuscation exceptions must map source paths to reasons") + for path, reason in exceptions.items(): + if path not in sources: + raise ValueError("Unknown or stale obfuscation exception: " + path) + if not isinstance(reason, str) or not reason.strip(): + raise ValueError("Obfuscation exception requires a reason: " + path) + result = {} + for path, source in sources.items(): + if path in exceptions: + continue + data = companion(json.loads(source.read_text())) + relative = Path(path).with_name(data["name"] + ".json") + result[relative] = (json.dumps(data, indent=2, ensure_ascii=False) + "\n").encode() + return result + + +def synchronize(cases, exceptions_path, check=True): + expected = expected_companions(cases, exceptions_path) + generated = cases / "obfuscation" / "generated" + actual = {p.relative_to(generated): p.read_bytes() for p in generated.rglob("*.json")} + errors = [] + for path in sorted(expected.keys() | actual.keys()): + if path not in expected: + errors.append("Orphaned obfuscated companion: " + str(path)) + elif path not in actual: + errors.append("Missing obfuscated companion: " + str(path)) + elif expected[path] != actual[path]: + errors.append("Stale obfuscated companion: " + str(path)) + if not check: + for path in actual.keys() - expected.keys(): + (generated / path).unlink() + for path, content in expected.items(): + output = generated / path + output.parent.mkdir(parents=True, exist_ok=True) + output.write_bytes(content) + return [] + return errors + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--write", action="store_true", help="Regenerate committed companions") + parser.add_argument("--check", action="store_true", help="Verify companions without writing (default)") + parser.add_argument("--cases", type=Path, default=Path("precomputed-assignments/cases")) + parser.add_argument("--exceptions", type=Path, + default=Path("precomputed-assignments/obfuscation-exceptions.json")) + args = parser.parse_args() + if args.write and args.check: + parser.error("Choose --write or --check") + try: + errors = synchronize(args.cases, args.exceptions, check=not args.write) + except (ValueError, KeyError, TypeError, OSError) as error: + parser.exit(1, str(error) + "\n") + if errors: + parser.exit(1, "\n".join(errors) + "\nRun python3 ci/precomputed_obfuscation.py --write\n") + print("Obfuscated companions regenerated" if args.write else "Obfuscated companions are synchronized") + + +if __name__ == "__main__": + main() diff --git a/ci/test_precomputed_obfuscation.py b/ci/test_precomputed_obfuscation.py new file mode 100644 index 00000000..505f8185 --- /dev/null +++ b/ci/test_precomputed_obfuscation.py @@ -0,0 +1,115 @@ +import copy +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +import precomputed_obfuscation as obfuscation + + +class ObfuscatedCompanionsTest(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name) + self.cases = self.root / "cases" + self.source = self.cases / "evaluator" / "case.json" + self.source.parent.mkdir(parents=True) + self.fixture = { + "name": "case", "description": "A normal case", + "response": {"data": {"attributes": {"flags": {"flag": {"variationValue": 7}}}}}, + "evaluations": [{"flag": "flag", "expectedResult": {"value": 7}}], + "expectations": {"exposures": [{"_count": 1, "flag": {"key": "flag"}}]}, + } + self.source.write_text(json.dumps(self.fixture)) + self.exceptions = self.root / "exceptions.json" + self.exceptions.write_text("{}") + + def sync(self, check=True): + return obfuscation.synchronize(self.cases, self.exceptions, check) + + def test_missing_companion_fails_cli(self): + result = subprocess.run([sys.executable, str(Path(obfuscation.__file__)), "--check", + "--cases", str(self.cases), "--exceptions", str(self.exceptions)], + capture_output=True, text=True) + self.assertEqual(1, result.returncode) + self.assertIn("Missing obfuscated companion", result.stderr) + + def test_new_nested_source_is_discovered(self): + self.sync(False) + new = self.source.parent / "nested" / "new.json" + new.parent.mkdir() + new.write_text(json.dumps({**self.fixture, "name": "new"})) + self.assertIn("Missing obfuscated companion: evaluator/nested/obfuscation-new.json", self.sync()) + self.sync(False) + self.assertEqual([], self.sync()) + + def test_changed_source_or_generated_expectations_fail(self): + self.sync(False) + self.fixture["evaluations"][0]["expectedResult"]["value"] = 9 + self.source.write_text(json.dumps(self.fixture)) + self.assertTrue(self.sync()) + self.sync(False) + target = next((self.cases / "obfuscation").rglob("*.json")) + target.write_text(target.read_text().replace('"value": 9', '"value": 8')) + self.assertTrue(self.sync()) + + def test_orphan_is_reported_and_removed(self): + self.sync(False) + new = self.source.with_name("other.json") + new.write_text(json.dumps({**self.fixture, "name": "other"})) + self.sync(False) + new.unlink() + self.assertTrue(any("Orphaned" in error for error in self.sync())) + self.sync(False) + self.assertEqual([], self.sync()) + + def test_only_wire_keys_and_encoding_metadata_change(self): + source = copy.deepcopy(self.fixture) + source["_include"] = [{"platform": "ios"}, {"platform": "unity"}] + source["evaluations"][0]["_skip"] = [{"platform": "ios"}] + result = obfuscation.companion(source) + self.assertEqual(["ios"], [p["platform"] for p in result["_include"]]) + self.assertEqual(source["evaluations"], result["evaluations"]) + self.assertEqual(source["expectations"], result["expectations"]) + self.assertIn("flag", source["response"]["data"]["attributes"]["flags"]) + + def test_skip_filter_is_preserved_by_intersection(self): + self.fixture["_skip"] = [{"platform": "android"}] + result = obfuscation.companion(self.fixture) + self.assertEqual(["ios", "flutter"], [p["platform"] for p in result["_include"]]) + self.assertNotIn("_skip", result) + + def test_exception_requires_existing_source_and_reason(self): + for entries in [{"evaluator/case.json": ""}, {"deleted.json": "Not applicable"}]: + self.exceptions.write_text(json.dumps(entries)) + with self.assertRaises(ValueError): + self.sync() + self.exceptions.write_text(json.dumps({"evaluator/case.json": "Wire-envelope rejection only"})) + self.assertEqual([], self.sync()) + + def test_untransformable_source_requires_explicit_exception(self): + self.fixture["response"]["data"]["attributes"]["flags"] = None + self.source.write_text(json.dumps(self.fixture)) + with self.assertRaises(ValueError): + self.sync() + + def test_known_digest_vectors_are_independent_of_generation(self): + vectors = { + "flag": "9817872c144b018abd77e3915bd77e2c27f4f534dccff8ffaca27361e3a5e1ee", + "Flag": "adca75d2141c51b0c0f084c1058edfb8e6e763f91aaf54ca06326d9847bd9586", + " flag ": "b1a5f851cc82a3fdf03a461d72a2241584dcf455df1d384e02a937901b3bc80b", + "caf\u00e9": "3bfa8c3c17c1b61035b98ecf14007cdf5cba5ce61a48e8cfb65f8106541892d3", + "cafe\u0301": "1e8b7ec5e8028a1ec96b38dd37f6ccea041c0a90358904af40ac5810c23c8765", + "\U0001f6b2/\u65d7": "94b611e0d3b26b52f6ad66013d1c72f8a92ea109390049759e75d3c8d3aa4dab", + "a\0b": "bac134d201be5e7f28fc7019248f0809c2a013b137e866446ee71add2bc344c7", + "": "072d985b427f536ad0a11b2d4c5e0f7e6f0ff6d23e779e082f75599ce3fe3eba", + } + for key, expected in vectors.items(): + self.assertEqual(expected, obfuscation.encode_key(key)) + + +if __name__ == "__main__": + unittest.main() diff --git a/precomputed-assignments/README.md b/precomputed-assignments/README.md index cf76ab45..380812a7 100644 --- a/precomputed-assignments/README.md +++ b/precomputed-assignments/README.md @@ -213,6 +213,40 @@ emit one event with `evaluation_count: 2`. Exposure deduplication is independent Runners must inspect real event records, not synthesize them from expectations. Keep native bridge and deployed-backend proof separate from mocked conformance. +## Plain and obfuscated coverage + +Every plain response fixture has a generated obfuscated companion. This checks +that encoding preserves public evaluation results and emitted events. Dedicated +malformed-encoding cases, unsupported-consumer cases, and fixed hash vectors +remain separate. They do not require plain counterparts. + +Edit the plain fixture, then regenerate its companions: + +```bash +python3 ci/precomputed_obfuscation.py --write +python3 ci/precomputed_obfuscation.py --check +python3 ci/validate-precomputed-fixtures.py +``` + +The generator discovers plain JSON files recursively. It changes response-map +keys and encoding metadata. Public lookup keys, values, results, and event +expectations stay unchanged. The generated platform filter intersects the +source filter with consumers that support obfuscation. Evaluation and event +filters remain unchanged. + +Generated files are under `cases/obfuscation/generated/`, preserving source +categories and nested paths. Do not edit these files directly. CI compares them +with freshly generated content and rejects missing, stale, or orphaned files. +Adding a normal case needs no generator list or SDK runner change. + +When encoding does not apply, add the source path and a reason to +`obfuscation-exceptions.json`. CI rejects blank reasons and stale paths. An SDK +failure is not a reason for an exception. All current plain fixtures have +companions; the exception map is empty. + +The independent digest vectors remain fixed. Generator regression tests check +those known digests, including Unicode, whitespace, and embedded NUL keys. + ## Snapshot maintenance Consumers can copy `ci/sync-precomputed-fixtures.py` and configure the destination diff --git a/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json b/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json new file mode 100644 index 00000000..a8fb1e7f --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json @@ -0,0 +1,106 @@ +{ + "name": "obfuscation-all-doLog-false", + "description": "Generated obfuscated companion: All flags have doLog=false. Evaluations succeed but zero exposure events should be emitted.", + "context": { + "targetingKey": "silent-user", + "attributes": { + "plan": "internal" + } + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "84a2ac6cb08b06570e965cb98d55a0a83b415e1810ef3f4e103cd8fcde14ad7d": { + "allocationKey": "alloc-silent", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "2fa983b9b25a656f30c3c1ca3547327d0c91abb8dd5a968cbf737fe8b4def524": { + "allocationKey": "alloc-silent", + "variationKey": "greeting", + "variationType": "string", + "variationValue": "hello", + "reason": "TARGETING_MATCH", + "doLog": false + }, + "94501ab4c2a58e69a11081f73e5f61a1f37c9c88fa95e61555a9d14ee1dd4024": { + "allocationKey": "alloc-silent", + "variationKey": "limit-ten", + "variationType": "integer", + "variationValue": 10, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.silent.bool", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "on" + } + }, + { + "flag": "fixture.silent.string", + "variationType": "string", + "defaultValue": "default", + "expectedResult": { + "value": "hello", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "greeting" + } + }, + { + "flag": "fixture.silent.int", + "variationType": "integer", + "defaultValue": 0, + "expectedResult": { + "value": 10, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "limit-ten" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 3 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json b/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json new file mode 100644 index 00000000..c5a6e355 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json @@ -0,0 +1,82 @@ +{ + "name": "obfuscation-duplicate-evaluation", + "description": "Generated obfuscated companion: The same flag is evaluated twice. Validates that the SDK returns consistent values and that exposure dedup produces only one exposure event. Web SDK's FlagEvaluationAggregator aggregates duplicate evaluations into one event with evaluation_count=2; the web override and expectations.events matcher verify this. Non-aggregating platforms emit two separate events.", + "context": { + "targetingKey": "dedup-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "491646f7138ee482b2ace2d2834ff8ff34b20c8ba8cd329156857ab1be6154a6": { + "allocationKey": "alloc-dedup", + "variationKey": "variant-a", + "variationType": "string", + "variationValue": "alpha", + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.dedup.flag", + "variationType": "string", + "defaultValue": "default", + "expectedResult": { + "value": "alpha", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant-a" + } + }, + { + "flag": "fixture.dedup.flag", + "variationType": "string", + "defaultValue": "default", + "expectedResult": { + "value": "alpha", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant-a" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], + "evaluationEvents": [ + { + "_count": 1, + "evaluation_count": 2 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json b/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json new file mode 100644 index 00000000..4c038b04 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json @@ -0,0 +1,142 @@ +{ + "name": "obfuscation-serial-id-on-exposure", + "description": "Generated obfuscated companion: Validates that serialId from the precomputed assignment is forwarded as serial_id on the exposure event. Covers: present serialId, absent serialId, serialId of 0 (must not be treated as absent), and null serialId.", + "context": { + "targetingKey": "serial-id-test-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "flags": { + "8c7c04076517d2c3c0633fda9a6664d4b114cb359cc512dd38c4ef69373032fd": { + "allocationKey": "holdout-allocation", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true, + "serialId": 340132 + }, + "36d69e5a88de3b456cd6bfca1070ee06c667c9c94d7de9022616661c71928e50": { + "allocationKey": "regular-allocation", + "variationKey": "off", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9c7eac87f0bcefab56284d70561f954ffccd2f88fcd895067ffc94832023d3be": { + "allocationKey": "zero-allocation", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true, + "serialId": 0 + }, + "6597def4cb332745af4ade37ee996a8958091ccadc58082c2c49c4db66c24e98": { + "allocationKey": "null-allocation", + "variationKey": "off", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true, + "serialId": null + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "serial.id.flag", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "on" + } + }, + { + "flag": "no.serial.id.flag", + "variationType": "boolean", + "defaultValue": true, + "expectedResult": { + "value": false, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "off" + } + }, + { + "flag": "zero.serial.id.flag", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "on" + } + }, + { + "flag": "null.serial.id.flag", + "variationType": "boolean", + "defaultValue": true, + "expectedResult": { + "value": false, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "off" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "serial_id": 340132 + }, + { + "_count": 1, + "serial_id": null + }, + { + "_count": 1, + "serial_id": 0 + }, + { + "_count": 1, + "serial_id": null + } + ], + "evaluationEvents": [ + { + "_count": 4 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json new file mode 100644 index 00000000..8b20e3f0 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json @@ -0,0 +1,161 @@ +{ + "name": "obfuscation-all-types-success", + "description": "Generated obfuscated companion: Successful precomputed assignments for all supported Flutter value types.", + "context": { + "targetingKey": "precomputed-user", + "attributes": { + "plan": "pro", + "platform": "flutter" + } + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "657bbcc45263793d67e4f7967fdd257d6e1896cc095d758c520ca3f52beca003": { + "allocationKey": "allocation-success", + "variationKey": "enabled", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "abf42b7b192cf3a719bb8ea77c9ff652c82bd4c7aba54561e374efeb2fbbb9b7": { + "allocationKey": "allocation-success", + "variationKey": "copy-a", + "variationType": "string", + "variationValue": "Datadog Flags", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "820dab9a17046e7bf33d080bcb6f44547e1b92c31b36e7d9dcfde96c46bb5d56": { + "allocationKey": "allocation-success", + "variationKey": "limit-five", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f4d559502705ccaba0a55b0defc37636fe7ed397a477a07389771c07183f139b": { + "allocationKey": "allocation-success", + "variationKey": "half", + "variationType": "float", + "variationValue": 0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "41057f3677d1a6c229d214c630b71e9f499abbb68116d50594561663f6ec09d9": { + "allocationKey": "allocation-success", + "variationKey": "object-a", + "variationType": "object", + "variationValue": { + "showBanner": true, + "colors": [ + "blue", + "green" + ] + }, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "flutter.fixture.enabled", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "enabled" + } + }, + { + "flag": "flutter.fixture.title", + "variationType": "string", + "defaultValue": "Fallback title", + "expectedResult": { + "value": "Datadog Flags", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "copy-a" + } + }, + { + "flag": "flutter.fixture.limit", + "variationType": "integer", + "defaultValue": 0, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "limit-five" + } + }, + { + "flag": "flutter.fixture.ratio", + "variationType": "float", + "defaultValue": 0.0, + "expectedResult": { + "value": 0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "half" + } + }, + { + "flag": "flutter.fixture.config", + "variationType": "object", + "defaultValue": {}, + "expectedResult": { + "value": { + "showBanner": true, + "colors": [ + "blue", + "green" + ] + }, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "object-a" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 5 + } + ], + "evaluationEvents": [ + { + "_count": 5 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json new file mode 100644 index 00000000..b9a7541d --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json @@ -0,0 +1,130 @@ +{ + "name": "obfuscation-defaults-and-emission-gates", + "description": "Generated obfuscated companion: Precomputed assignments that validate exposure gates, unknown variation isolation, and default/error flagevaluation payloads. Unsupported variation types are omitted; lookup returns FLAG_NOT_FOUND.", + "context": { + "targetingKey": "precomputed-user", + "attributes": { + "plan": "free", + "platform": "flutter" + } + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "9176965ed24adf9f9e56f8918f3e4f7d5178ab57ca0fdb3e32266a4203930e49": { + "allocationKey": "allocation-silent", + "variationKey": "silent-on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "f83c743fa9fc2faf137298f504966877bbe7d23e7cb9380c4b73188b764b0a0c": { + "allocationKey": "allocation-string", + "variationKey": "copy-b", + "variationType": "string", + "variationValue": "Actual string", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1345780c7d221b7c1798057d61113f43466cb92b31ecb05fa74ee4b511d7c482": { + "allocationKey": "allocation-unknown", + "variationKey": "unknown-a", + "variationType": "unsupported", + "variationValue": "Ignored", + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "flutter.fixture.silent", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "silent-on" + } + }, + { + "flag": "flutter.fixture.string", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": false, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "flutter.fixture.unknown", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": false, + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + }, + { + "flag": "flutter.fixture.missing", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "fallback", + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "evaluationEvents": [ + { + "_count": 4, + "_skip": [ + { + "platform": "web" + } + ] + }, + { + "_count": 1, + "_include": [ + { + "platform": "web" + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json new file mode 100644 index 00000000..c6851679 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json @@ -0,0 +1,83 @@ +{ + "name": "obfuscation-empty-flags-response", + "description": "Generated obfuscated companion: Server returns an empty flags object. All evaluations must return their default values with a flagNotFound error.", + "context": { + "targetingKey": "empty-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": {}, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.bool.missing", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": false, + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + }, + { + "flag": "fixture.string.missing", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "fallback", + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + }, + { + "flag": "fixture.int.missing", + "variationType": "integer", + "defaultValue": 0, + "expectedResult": { + "value": 0, + "reason": "ERROR", + "errorCode": "FLAG_NOT_FOUND", + "variantKey": null + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "evaluationEvents": [ + { + "_count": 3, + "_skip": [ + { + "platform": "web" + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json new file mode 100644 index 00000000..e6bd5b9e --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json @@ -0,0 +1,148 @@ +{ + "name": "obfuscation-falsy-values", + "description": "Generated obfuscated companion: Flags whose variation values are falsy (false, 0, 0.0, empty string, empty object). SDK must return these as real values, not fall back to defaults.", + "context": { + "targetingKey": "falsy-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "fc0d169622e17ab8efff1da7fe0d0ff124de893d2e723fb9a8c632a87f5617e2": { + "allocationKey": "alloc-falsy", + "variationKey": "off", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7a856abf0d6b1d857471bdfe3f81e7e0c08a1c7da13315ffd303a60e40032c8d": { + "allocationKey": "alloc-falsy", + "variationKey": "zero", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "8e902056a9d96a536e0300af89c407afe670d48fac2ae3ed9c4f0805579011f0": { + "allocationKey": "alloc-falsy", + "variationKey": "zero-f", + "variationType": "float", + "variationValue": 0.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7aa973f2d2a8f5ff69c54faad155bbad70b0c2733f20d10c0ed170749ce60f3c": { + "allocationKey": "alloc-falsy", + "variationKey": "empty", + "variationType": "string", + "variationValue": "", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "5273f6912bea9ecac5cd7d11674e39b208fa0bc796a3379e5b46138a4d2771a3": { + "allocationKey": "alloc-falsy", + "variationKey": "empty-obj", + "variationType": "object", + "variationValue": {}, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.falsy.bool", + "variationType": "boolean", + "defaultValue": true, + "expectedResult": { + "value": false, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "off" + } + }, + { + "flag": "fixture.falsy.int", + "variationType": "integer", + "defaultValue": 99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "zero" + } + }, + { + "flag": "fixture.falsy.float", + "variationType": "float", + "defaultValue": 1.0, + "expectedResult": { + "value": 0.0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "zero-f" + } + }, + { + "flag": "fixture.falsy.string", + "variationType": "string", + "defaultValue": "non-empty", + "expectedResult": { + "value": "", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "empty" + } + }, + { + "flag": "fixture.falsy.object", + "variationType": "object", + "defaultValue": { + "fallback": true + }, + "expectedResult": { + "value": {}, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "empty-obj" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 5 + } + ], + "evaluationEvents": [ + { + "_count": 5 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json new file mode 100644 index 00000000..8ab272bb --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json @@ -0,0 +1,66 @@ +{ + "name": "obfuscation-integer-precision-native-only", + "description": "Generated obfuscated companion: Validates that SDKs with 64-bit integer flag evaluation preserve values outside JavaScript's safe integer range (Number.MAX_SAFE_INTEGER = 2^53-1).", + "context": { + "targetingKey": "precomputed-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "60c239d3d5039428009ffc2a7918ccbd9c6485ee75e7d3130b148103d3e784ae": { + "allocationKey": "allocation-a", + "variationKey": "large", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.large-int", + "variationType": "integer", + "defaultValue": 0, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "large" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], + "evaluationEvents": [ + { + "_count": 1 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json new file mode 100644 index 00000000..0883414b --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json @@ -0,0 +1,490 @@ +{ + "name": "obfuscation-multi-flag-set", + "description": "Generated obfuscated companion: Precomputed response with 50 flags of mixed types. Validates that SDK parsers handle moderate flag sets without truncation or memory errors. Only a sample of flags are evaluated; the remainder are parsed but not asserted.", + "context": { + "targetingKey": "large-set-user", + "attributes": { + "plan": "enterprise" + } + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "bf9be622ca7517e32cd02c784169beb8e5102892504fdad8384e348d48e36879": { + "allocationKey": "alloc-l", + "variationKey": "v1", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "75dcd247147ab21d09a4fc73051324b2000bd0bf97d82d35ad6d80eaa6d07973": { + "allocationKey": "alloc-l", + "variationKey": "v2", + "variationType": "string", + "variationValue": "two", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "32681eee3e6ebf4ebaa0b03b91638cbae0920b26239bdea4fa2b7d0bb3a46df3": { + "allocationKey": "alloc-l", + "variationKey": "v3", + "variationType": "integer", + "variationValue": 3, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "792f76037d724266f9c00e971967bbfe62cba43274b31f652b9768d6381e7ace": { + "allocationKey": "alloc-l", + "variationKey": "v4", + "variationType": "boolean", + "variationValue": false, + "reason": "DEFAULT", + "doLog": true + }, + "59bf45e79a60fa4a33f2d4c639ef910982d3d3e29ca2fb9c147fb4977569f35a": { + "allocationKey": "alloc-l", + "variationKey": "v5", + "variationType": "string", + "variationValue": "five", + "reason": "SPLIT", + "doLog": true + }, + "fce91fe1f119ec27d34a9cb77ab5014d49153c8afa8162b282c9c595591ff130": { + "allocationKey": "alloc-l", + "variationKey": "v6", + "variationType": "float", + "variationValue": 6.6, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "086529987ece3ae0319c12332405bbec6e6eb913266ade8c8fb33b2b7b89ab5f": { + "allocationKey": "alloc-l", + "variationKey": "v7", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1d89f79a25e4eae7f080cb86d35af66fd2147336f6c450dcd40d23333f83d888": { + "allocationKey": "alloc-l", + "variationKey": "v8", + "variationType": "string", + "variationValue": "eight", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "0fe82d095e9c1dbefdca57b310ab4c19c80439c87a41b0eef548685914df239d": { + "allocationKey": "alloc-l", + "variationKey": "v9", + "variationType": "integer", + "variationValue": 9, + "reason": "DEFAULT", + "doLog": true + }, + "1ac48586eced16b0aff76e17312da68f2c43fd3cf9ca33d76f3e427f93797c4b": { + "allocationKey": "alloc-l", + "variationKey": "v10", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "cc6234a3430646b1df0c9e2bcca80dfe16b05abcca88d58f5f97c19a14a9a320": { + "allocationKey": "alloc-l", + "variationKey": "v11", + "variationType": "string", + "variationValue": "eleven", + "reason": "SPLIT", + "doLog": true + }, + "215f50d3c5e5908c31d2bed80414ce26ba68b264ea5baa4ab5b743e1833c1945": { + "allocationKey": "alloc-l", + "variationKey": "v12", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "88c23388c4cec50c57429698b96c975be9f8915f88308fba6bda54acc6d01998": { + "allocationKey": "alloc-l", + "variationKey": "v13", + "variationType": "boolean", + "variationValue": false, + "reason": "DEFAULT", + "doLog": false + }, + "b66bc4f899fa5ad230fa050a3ba35ac5c5cda22bebecbb44e6c63afddbaf7b5b": { + "allocationKey": "alloc-l", + "variationKey": "v14", + "variationType": "string", + "variationValue": "fourteen", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "83ab766d8522b069512a37e2a5bd8bd8f941fbeef306eab09f6247e8db3b725b": { + "allocationKey": "alloc-l", + "variationKey": "v15", + "variationType": "integer", + "variationValue": 15, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "db1c7d8f6ac65f0e493bb4c3a87f153ec6cac18bd0c984b20ff09619ff79cce9": { + "allocationKey": "alloc-l", + "variationKey": "v16", + "variationType": "boolean", + "variationValue": true, + "reason": "SPLIT", + "doLog": true + }, + "4d23aa03aec589f62d46d15642b79f6e3c87e3aefa5b49cd319304f4a013ac95": { + "allocationKey": "alloc-l", + "variationKey": "v17", + "variationType": "string", + "variationValue": "seventeen", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "023b47247033e8bea815ff0cc16255bebdff082b3cfb1eeb4a9b55b3004276e1": { + "allocationKey": "alloc-l", + "variationKey": "v18", + "variationType": "float", + "variationValue": 18.0, + "reason": "DEFAULT", + "doLog": true + }, + "0b2b70d8c98c0731692191e78b527512e548190d97d710d7f0fc8f19cd05913d": { + "allocationKey": "alloc-l", + "variationKey": "v19", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9c1796f3cb195313519a837ead5d54e083195b6206e189bbdd0220561b4ca766": { + "allocationKey": "alloc-l", + "variationKey": "v20", + "variationType": "object", + "variationValue": { + "count": 20 + }, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f73dce8e960f6d67023c407ac490ce3605b279288b56f78031bfed36f0014935": { + "allocationKey": "alloc-21", + "variationKey": "var-21", + "variationType": "boolean", + "variationValue": true, + "reason": "DEFAULT", + "doLog": false + }, + "c078d7577eff8d7a6fc8963d0bd16fbb172c3a4a3aeb9e714c75a2574d7967e1": { + "allocationKey": "alloc-22", + "variationKey": "var-22", + "variationType": "string", + "variationValue": "value", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6326ec0ed3c2465e7cc807f46c743037e7095d3f8e9d1d56dbaea76b2b00452b": { + "allocationKey": "alloc-23", + "variationKey": "var-23", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + }, + "3d3d982d7910fda9264321566138165779600cddbf42d215d689080b617b3fd0": { + "allocationKey": "alloc-24", + "variationKey": "var-24", + "variationType": "float", + "variationValue": 1.5, + "reason": "DEFAULT", + "doLog": false + }, + "5a80d2d736721d87db810627614f4f6b127ee0a332f5fdea8550a78e3ec4e699": { + "allocationKey": "alloc-25", + "variationKey": "var-25", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "86edc8f5b58892c55f5fde77e66cdc7b6d0a9b071e2677d8fa16dbb8f409f380": { + "allocationKey": "alloc-26", + "variationKey": "var-26", + "variationType": "string", + "variationValue": "value", + "reason": "SPLIT", + "doLog": true + }, + "cc6b19cce7d683983134195f55a0c6bf3a5f844944e283cda1c58485b5a2dd2a": { + "allocationKey": "alloc-27", + "variationKey": "var-27", + "variationType": "integer", + "variationValue": 42, + "reason": "DEFAULT", + "doLog": false + }, + "4c2f7e12bea6b91c5d5c9862de0b24418f6fde074eb5e017c93293cfabf60060": { + "allocationKey": "alloc-28", + "variationKey": "var-28", + "variationType": "float", + "variationValue": 1.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "5a48e1a5c7c8e185b62cd259351f4abe443771f07292eefbd49f0599881e35ce": { + "allocationKey": "alloc-29", + "variationKey": "var-29", + "variationType": "boolean", + "variationValue": true, + "reason": "SPLIT", + "doLog": true + }, + "cf6321f08df13c06652debb11c4cb9ea67e215e5a364d845f30d865554876b4d": { + "allocationKey": "alloc-30", + "variationKey": "var-30", + "variationType": "string", + "variationValue": "value", + "reason": "DEFAULT", + "doLog": false + }, + "1660da21238865ae03e7c5c4959d02a34bc210101d7608f7221f97f254b801d0": { + "allocationKey": "alloc-31", + "variationKey": "var-31", + "variationType": "integer", + "variationValue": 42, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "34529807d59d7bf904c56e17e112ed555687ee05a332a9ef957d7f029870ed57": { + "allocationKey": "alloc-32", + "variationKey": "var-32", + "variationType": "float", + "variationValue": 1.5, + "reason": "SPLIT", + "doLog": true + }, + "f8fcf4b3a145194a19daabe0521e6cfea4bac6b20a6fd729f4c8c3ad6ac3e6f6": { + "allocationKey": "alloc-33", + "variationKey": "var-33", + "variationType": "boolean", + "variationValue": true, + "reason": "DEFAULT", + "doLog": false + }, + "210a549229e78c66c588dffc37d99918e7968a04876fa87cd1b719a5dfe1edf2": { + "allocationKey": "alloc-34", + "variationKey": "var-34", + "variationType": "string", + "variationValue": "value", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ffd5bbbf90dd7c225ddc95b0c1134cee255c5d1ee7f7889bf2c239b99dd43db4": { + "allocationKey": "alloc-35", + "variationKey": "var-35", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + }, + "929200b0fd1248b310ac0f0785a5a3ee3b1aa40cbd4cc7d7ee7c141bd57c839b": { + "allocationKey": "alloc-36", + "variationKey": "var-36", + "variationType": "float", + "variationValue": 1.5, + "reason": "DEFAULT", + "doLog": false + }, + "4aefe68d39be982605014ac90b746de9be85b4bea79cacc26459cdc06b96f48f": { + "allocationKey": "alloc-37", + "variationKey": "var-37", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6ba21a1d31e1f9074d322885263107157affa2265582bf6a05f84441b8c118eb": { + "allocationKey": "alloc-38", + "variationKey": "var-38", + "variationType": "string", + "variationValue": "value", + "reason": "SPLIT", + "doLog": true + }, + "77581b9776c4d5afa1919a1e0c758067d07c3968afa2201870cea6dae6579aa3": { + "allocationKey": "alloc-39", + "variationKey": "var-39", + "variationType": "integer", + "variationValue": 42, + "reason": "DEFAULT", + "doLog": false + }, + "03461ca38265adbdcede804d0ba7a544dce9c4de6296eb32d8a6683096a9b1ce": { + "allocationKey": "alloc-40", + "variationKey": "var-40", + "variationType": "float", + "variationValue": 1.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "92325ac72dff844afbdc8a8a8ca939b673fbb452cd67d7b970990c58596baf57": { + "allocationKey": "alloc-41", + "variationKey": "var-41", + "variationType": "boolean", + "variationValue": true, + "reason": "SPLIT", + "doLog": true + }, + "934025c0b96e851d348e09166362375a53e2776f595da60d4e2fde30f1503baf": { + "allocationKey": "alloc-42", + "variationKey": "var-42", + "variationType": "string", + "variationValue": "value", + "reason": "DEFAULT", + "doLog": false + }, + "f6d270502e6f9fc1f4cfa7fbcc78e2b3377d9fb480f23e864cd4be0c96ec3467": { + "allocationKey": "alloc-43", + "variationKey": "var-43", + "variationType": "integer", + "variationValue": 42, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "8bb8e9b84c7ab5397d4012c8d58cb797a8fac7562c86ede747d91f5971bfef10": { + "allocationKey": "alloc-44", + "variationKey": "var-44", + "variationType": "float", + "variationValue": 1.5, + "reason": "SPLIT", + "doLog": true + }, + "81be2644335d094121dc49dafd671c0c212980253962dd2f24986736cdaf9583": { + "allocationKey": "alloc-45", + "variationKey": "var-45", + "variationType": "boolean", + "variationValue": true, + "reason": "DEFAULT", + "doLog": false + }, + "b4fa3c55cb8c05baba5ca43eac146e6036b0028e2fb07eafd6819d3935c9e4bc": { + "allocationKey": "alloc-46", + "variationKey": "var-46", + "variationType": "string", + "variationValue": "value", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a56d4714d20368ac54afa5225b1dec452d9bc102f604fd54bd338e4b693bda38": { + "allocationKey": "alloc-47", + "variationKey": "var-47", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + }, + "e5b919481d265c3573d94171004324cff98c4e223efd7b568b028ffcd4314a6e": { + "allocationKey": "alloc-48", + "variationKey": "var-48", + "variationType": "float", + "variationValue": 1.5, + "reason": "DEFAULT", + "doLog": false + }, + "370b0fcd16be8003ef1d1e1f39e7e19b3698f17a24957ed654420be0f81eddc5": { + "allocationKey": "alloc-49", + "variationKey": "var-49", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "0721adc36960473f00919092dc06adc2988d409a72f5244bc47a4b11de285660": { + "allocationKey": "alloc-50", + "variationKey": "var-50", + "variationType": "string", + "variationValue": "value", + "reason": "SPLIT", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.large.flag-01", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "v1" + } + }, + { + "flag": "fixture.large.flag-10", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "v10" + } + }, + { + "flag": "fixture.large.flag-20", + "variationType": "object", + "defaultValue": {}, + "expectedResult": { + "value": { + "count": 20 + }, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "v20" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 3 + } + ], + "evaluationEvents": [ + { + "_count": 3 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json new file mode 100644 index 00000000..4002b724 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json @@ -0,0 +1,110 @@ +{ + "name": "obfuscation-multiple-reasons", + "description": "Generated obfuscated companion: Flags with different evaluation reasons in a single response: TARGETING_MATCH, DEFAULT, and SPLIT.", + "context": { + "targetingKey": "reason-user", + "attributes": { + "plan": "pro" + } + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "19f9605f249b328efe6542c0a39acdaa7a0b39c61daccb3883f2cf3c5705e9a3": { + "allocationKey": "alloc-target", + "variationKey": "targeted-on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e433cf607656e33013abf122d4f7576389f0acf3ea88f085fd069bd839390a4f": { + "allocationKey": "alloc-default", + "variationKey": "default-val", + "variationType": "string", + "variationValue": "baseline", + "reason": "DEFAULT", + "doLog": true + }, + "212fded283b3498c3742391c9db4843ff32aad02ded7646c68dd14bc008f3f03": { + "allocationKey": "alloc-split", + "variationKey": "bucket-a", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.reason.targeted", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "targeted-on" + } + }, + { + "flag": "fixture.reason.default", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "baseline", + "reason": "DEFAULT", + "errorCode": null, + "variantKey": "default-val" + } + }, + { + "flag": "fixture.reason.split", + "variationType": "integer", + "defaultValue": 0, + "expectedResult": { + "value": 42, + "reason": "SPLIT", + "errorCode": null, + "variantKey": "bucket-a" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 3 + } + ], + "evaluationEvents": [ + { + "_count": 3 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json similarity index 93% rename from precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json rename to precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json index 4ad1d694..90483ddb 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-float.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-boundaries-float", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "2aec5b3d4477769702ec52c42349be07a31c413ebffb69196b762523adf205ab": { + "35dd7544a6b9a875c5074a893aa3d5c95f015b6cff7f07f30390bc90b939839e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ccd840e29b0846bbc8ace7da7bc20c300b2a14e1f5f68961f2fb76e69fb0ba04": { + "54b3e4e77e354d4e049991350fe68ce1a90ba4e874933a9e269d5b99cb3ad115": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ebe608c5e7ab0bf15e0c6585810759616ab5881130d7ddb956ec8749c43a0939": { + "6cec42afe677ad01472e4754a611106037ddb8e786570bf9a730630b8d2f7f30": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "1f2de3a9ac37932ca8fbeaafe88046f8f7a49075c87eec5b805a0967706685a4": { + "8de4314e7ce11a9f64a3490e59968a1130f56b15c39276bdff9ebc344402101d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "431adeb8d8d06d55e943812fd5cf8bf6095858eb898fd3133999c3d163607b3d": { + "4182dc078ece15bc1c45cabf6e2ab2ad0b7acaf7f42eaa3ad63fe3956beb1db7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "2c7494350d45235245c36589fe36990e14f5e3eca4522e7c93b05ec4e4e3de7c": { + "ec7fd610e215fd5d177ffa68bec26d50a871bc1cf0114636a734e1660bd6411e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "19b8019e6dc73fdabe4884fd109c0c0865eb682b5a6e9b2c098d8698cb8a25bf": { + "53d34567f5337f1104d86a9e821df4511fa74a9cc03516f426c9ab4c7af0ac52": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "05dce5b4f6ec9f670d767a9e2f3095c91dc5f947c0cf65f26ffe0d34d169c27a": { + "3e69e701cc02087f7897b6f4832e8be0be9ca155876f8cb95597e6ad32b1e2ab": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "b31d785097e2b21ff4203b7f42d9601f0fe117e04ae52fd41c38ec226ee7c9dd": { + "bd1a3e85e06626dbb1c56432a8dea7a0128284f1d781013835a2564e70f87d98": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "2561c2c5e59e5ee958e65c5ffc5afdc82deab8caacc24b7fd955426d38f3f10a": { + "a212f6d288564e940e009835514eaad55a3d62caada53dd1fdda1b2475efed0a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a4378fa74480657ce942442b4755051dfe8254b3a869ef1d09e73b70905931ba": { + "f8dc9d43242ec89a4db2212c3a70ee9c938b0cb36afac45b2000cd7c77bf7609": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4c973da42e257593047a0e547423acae08bcb5d6529670d6917dc653d559878d": { + "b1e20092aa2b67b3e8ed9d5533a2260ff177207d9b86fac2151864b2d7af492e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -106,7 +106,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6291d2c93c31947490a82ac572b34ff0a2cfc6d1bf63ee929bd4c427e4c14e46": { + "45f0d04656e98eb32b8221714cf169d57479d2be4af6b96c5ffe0f9fd589ad0a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -114,7 +114,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ed9c529fd1165fc1f8560f540dd0741dd240473987473db250c027486ca54481": { + "680bb5138700a197f7896709b20d6a451854dfaf7fd2016374154dc214101f96": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -122,7 +122,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "87217664c8cdba2054ae7ba5e0513f1fe0932a93deb52ee46c8c4856db50bfaf": { + "5f0e609737393c76014f125cfe1d8b685065b3f8bb2833e393f4de4bb3f271fd": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -130,7 +130,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "435af2981e1d1cf3e2e762b9fbb1b99f4850d1b0d4bed9de0f44f0418b223506": { + "6de38d27b71396e8169bdf012bf53a9f6112b4f9533e492d1d0c372aa5b2b89a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -138,7 +138,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "608602ea98e8c47ffb9f86e51e8093daa81ff902841d774092b32df7f92421e2": { + "e68a8c78bf6d545e6c949e88209611b6615ddeea9817440de9fd3fb7a289c335": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -146,7 +146,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ae43ae724ec81be659b9cbd89315102b127b1b9e24e26def87496eb4a8aa38ab": { + "a6d6386fa18188d57ad46d118b937d77a1a8d814e3ff723871452b9d5f6abd03": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -154,7 +154,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "2358b34f81306a3acd892893294abea0c33721a65b06e355cda36c8e54739be5": { + "9d28b9ca14aed8cd8edab7144dc09ff50ff608f1676df0c92be1a225fc4c95b4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -162,7 +162,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9fc154f59fba732da1a2be979ec48044cd88f185fd90b9cfc306e53f3f6d6a88": { + "e026139b2c1ef067fcd194745a316b948ca17d4224a2c6ef0f54e42739a6f9e5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -170,7 +170,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "350edcb8f9921229dea893c8a3948457281d0ef88537c3c8905a4dd924165228": { + "9ad722f4aa0e6cbe77da0e6406a3f40ac0dbe3f556a524d1d504115896235fed": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -178,7 +178,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "3ff68a68d0c6c04348089c33052aad256a51086ae365496d7ecf89a55c9a4d70": { + "56cd8c449d33e0a19ca9b0d300b40bd994dfd1f4f3b16d093e453d8a97a3645f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -190,7 +190,7 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } @@ -1080,15 +1080,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json similarity index 93% rename from precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json rename to precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json index 9f6a4ef3..4acc8be2 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-integer.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-boundaries-integer", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "7c5c55bbc1d947ebd29d4d22bbb8e854fb84a3168ec16c9b88ff1eb8048f1259": { + "dba4f4d05c42d94f2f0fe7fb4c5622432cbace9983c4f2bcd81dba39a6327250": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "1db93bef085224805fc0b94e254d8efea2ad144e255512c7822ba1941efd37ca": { + "c053d531267e94db3094b514ac132333832cf45cc102899e23a317cfd1b86c7c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c65f42d21a69e9e63e768bb7c353ccc659d8fa7fda19d552c46571fe747939a1": { + "cc6b41d8abf103fc2c0903beb89ea4916203e65e34f56f229bd4bfd30fca2b44": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "57ea520510c0e0ffdf6c016032a1ad8fd7e930feddb56e512c5ba5e176bb747e": { + "2cd758e8f6b3d6069426eaf4fe83cad350062d6f3cf9fdff78af6814151ff35f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "3bae488ac7f7c5e64e8d80c5e5d3a01fede98ee9dc1dbfaaa1602bdb834e3199": { + "be1e08347c503a3116b8872a98f762ce80f8b2d5d9712855f553e9a8e41a0e24": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "d7e31a5af23eaed253fbab5d5ba775a49cc869265a762861623b9850ca45200e": { + "439b8c652a5b3d6bb6a8b5601f86c09f38f1ca344dfa0cd10904c41d3a0089ba": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e214fbcdc5fa2e6b53e80a737cf3b01bf720e1da6bdc8d1a8126c82fae131cc5": { + "52c0be60636eb11b987e1729b894a63f02463a408660d632004cd68b66c17966": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "f87c7ffcbfc101c063bc2f6533d4a73054a4c3e0cc59b865b6d9a738cded6776": { + "32ac57434b06ca630219caa81b6d6c685138920a8a9d1e6205055cff023f3179": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "24e2195a49630a3b88efcb5227d7044651ffa1e483006af5ef3e41db492ca96a": { + "21bae751d95202679767297ac5a668ff2588c260e0c1d93e0ddb494a3c6c44d9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "45316309ea93c9d4d63ebd704488bd9b862cb9d30303b54ccbc668a45f1a9cfd": { + "ceb204983395689386573ade9d3496ac4c07fde14fd7dbc965b51654af834df1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "107e31005fc784e93f438721722b248ed66b8ed91d13eefca267786102fe5a81": { + "c2b69fdede340468e31ce79ee511016e1f3f15fceaf2228fb6bde3c034cb14d9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ca603c439f5fa9a01c76f4febf58bf0b1034e100dd37fd1731c05f667409846e": { + "0a1f9c3aedb3a5721f4215c26cd403a7bca6d5fcba088d6ff8c5eb58a4168903": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -106,7 +106,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "bf801a713f62854431659efc54a0cb312dd601a8459d4390b13840fd7f414a02": { + "cf564971ee029d99592fad8d056f2d1cb13ff91e3e283dc2c16b3a644db90734": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -114,7 +114,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6c6bc2ace96267f8a839f207457b3d12b93f8211e7ce9a2f4ef9b2ddfc70412b": { + "482ab354d08783942a1cc1613d261773481b27647304272c9a895b3f82aef81b": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -122,7 +122,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "cd01bfe31bd7117a646dddc5be9732400e8ee0076cafd79c189115b8b3081950": { + "8ae623d95ec069f4263033a5c3c63cc41c8369a4ada5c2df1e72feb3472b3298": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -130,7 +130,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "fe4005d2eb3448257136861a88114cadcc85f06cb2ede14a36b581d4900e4a5e": { + "3ee628175449c6db9aeeb2b77f155f2af7f566ae1209ed32f564b4e74d37ffbe": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -138,7 +138,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "fb7e52e4c272d1e4e9602339c4f6a7094257194ea28d274314b8ca0a4aec639b": { + "1aaaeb79098a7feede64e211e53f192295e0d97db557223f5945bd869a9ec82c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -146,7 +146,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "de7cefc4a649b9c39f7fe31b49d5fa5f8ac6b792e2227717e0130dd127bbfd70": { + "1a9a9de8e21c848b2bfec22d14c770d8a3a176dd1ce39955a5b165bf1f851590": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -154,7 +154,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e084557c149a57fcf7e04041ad080c04586380baa9093a16dfee198a1ad51989": { + "c2dca5f665b034d94acdc589d0b6646e583561b3f05aeba0a0b6399297c8ccf1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -162,7 +162,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "f163106c94e153c44a0f9c882c90c6c5771e7df230fb847e3a9426bf18a8f312": { + "a014f0066d631022d1c7628bd1b990291f772710900955e0aab48543db7c3b2a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -170,7 +170,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "40aa0011039c7b0946bb1264199ba576db480b932c341fa5a224340056ec0976": { + "05ab2bff8b65224aff72672e380d66e022ff5f0c1b76e3231aaf172f6c82d467": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -178,7 +178,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "79ccc13c690d354291ea1fbee6c0b103d273130dd90bb6f3efe51840d1c562ae": { + "a10340fc40240dde6ba540065e6526b8c66b6fd78d116451b42201a9ba4c665d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -190,7 +190,7 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } @@ -1080,15 +1080,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json similarity index 93% rename from precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json rename to precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json index 96b428ad..27bfa451 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-boundaries-number.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-boundaries-number", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "b4ba8411e13132261ecb4f57a72e7ca330d2618c536f5546590abb0cc25b9c47": { + "4151b6612247033495b8370b980c847c1efe5dc3ddb008a57aaf9e8afb97694d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "82796957263227e5d42ba913e864aca7aa0f6c06cf30ef1bd6a52cf771a5680e": { + "c3c464e47d3e87d433d2a0b6a5ea9e9cea9ffb65c31a7022fc4be4c51a3e3663": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9889dbdc0d0d8741c09f2d9e1f6b764e37995c66e76452502ff7a73398dc8fdb": { + "d9adf2748e7c2bb330f6b321204a4ab3d61aa555dbeaddcb3d79fb20383e3840": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "20c38d5ef57bd5c41cc52d311ab1147afabfcdbb2abbdaf47f9c1d5f71736248": { + "a3e931eb60a9b70284b948912691bbf4e1f757d1f52c90d1623413a4ed05eaf7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a989158eea675eb49b1f62e15c2205b1a1168d9ed769e90e16ba8f43e65963c2": { + "56a5f99336ac7daaa327d074e3c1cf1389f3b0e198d8b6b0dc4a0773f37466b0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "fd02ac0da0683586431856ba869c1ff07669b94244e6c8a58c7e1ae8dcb3a04d": { + "996e91f621b0091b21aaceea06330f3bcc8a4639939a0b3cafd38cdd2d732bde": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "0c4bdf647950f33d16dace93f197e1891b73d767a5bc3be0cf1e51a115775f63": { + "5b95fc3592f62934e978d8f18e998120d1012da96a4ae3c7a3bc832442914bce": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "bb03a68efa88e918862e8a481ee25893c059765384e7a0835b022f742ecffc05": { + "fb8ab09c443a1fef7f78aa00069193131bdddd5f754c69837da77dde8bd53ab4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "596ef4687c078263b87dd17061e7471add9255b49814d72150bac75dce35787d": { + "13bc44aee0e07075a5f61e06bea48f9c7d7cfe240bbcf45d9922127c5fcbf57a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "920cb9612332743d61020ef481f4990bf0826040b3d087212a891115e9f30f22": { + "2b8ee396fbeba43a943135d2ea6962f4eee41f3af704609313b6241bf4911f20": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "62e88abd1cfc891df7f5703503077e82fb99db1e9be9b91d9a6b50333b4d1110": { + "9dec15ccf9649edff360ec3a223801aa6f804087a939b1d14b686a7fe34a0cbc": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a99b58377ef7b9e52d6e1c3befd549d61e1ca234eca3cd8d4720c16928d9a7a0": { + "b1aedbf12de210a49f962e5e8db09a065c2b328a7d2c48ce4c6e919325b9c0c5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -106,7 +106,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ff66a9c09d22765cf9397d3e156bce434a683325f21ab6df1c9b7359056d676a": { + "4a989c369519e210c4857cd50fd525e571cc89b7da322276ba05487c15b4849f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -114,7 +114,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9b15e9496c8eb9e19f2e8afc18ac298593ea8d45370e1fceb0f98b0f47e28fdb": { + "313de806bdcd55e96b13d12c28260bc5238057d4ed2a8ecc0294f8c7e234fa01": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -122,7 +122,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e0d4ba65f888bbf2b204e1ed4cc02d23b0e0c3f8a0f4f22b51501c7532d4781f": { + "1f5409b64eb99b424819230ddfb333b5f399a911840ab6b36f0a0938a438af25": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -130,7 +130,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9b5089501d028a056aae6a5de88452f98f79340ba32f765a3950c6d8c0a82a9d": { + "61f7deca99e29b35ab18cdf82001dcf64ea3b04caaa9fc751d835f1c70d25fd2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -138,7 +138,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "1706e7b05d435e118cb4dee1540e2109fd7a7b1b19b08fceec0cfebc8fc0abc1": { + "06503c386b2c84e0299e7a692baecdecf2b19698b90f13b89df7d4e309d43870": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -146,7 +146,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "3769e087d36053e2971961b47640ab9b7397f02b1eddda5d4e90819ef1b1f5bb": { + "6d9de4417c04ad9908dc846b1a9ae5c6c2fe214e7aca7e2dd3480d82abd46a5f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -154,7 +154,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e0cff618c57a7aca3a66fbbb36b72aa53a4d4b7a08a5c67a0e25ffd564cea395": { + "1b553f209415af2ef3dd59e7f7383e270e5090b9811e319c39645a51d58ec26c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -162,7 +162,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4d204756440205698d05663c5a511d991d8e7ad64f54b1a257034b8e2c52f80f": { + "4ccf6d4d18107b748ae8ec301e66adb190f15bd77e3bf23246d8994787c8a40c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -170,7 +170,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4c741509a1ac866498f61619846c2cdec7fd84bbc362ee6ff3f8d563f2156fc8": { + "9de54af153484d9db86d593c9a0c9443f7c5a88881964452b69b7f2d8a878c01": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -178,7 +178,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c9bbbec289c2c83e830e9bffd2ff577daf18bb71371cff7c08a5372ed860d957": { + "52b56cf56113aee71339a1256252ae52741aa5e666d2a5ca3ae3ad194cbb83f5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -190,7 +190,7 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } @@ -1080,15 +1080,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json similarity index 80% rename from precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json rename to precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json index fdbedade..cb5d7f40 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-float.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-float", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "43dcecc88807de972a354bd0b96946419ccf086a3cb1abcaef10a796129ffb7e": { + "6711bb1e8b093b846ea439322c667bce9ca0568b0f0d4320e05c236c9ead11ce": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ad5896fa25ecdb06cc985d99e469f2b867419c296d99142ba53b7a4ab11d0167": { + "dd28ccc7dc267c0927246a422010adbf6e81bb5b0e8fa6a3f9c5c999dcfa8cac": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5f920ae1126d6bf5dd3c7cde59b1604dfbae53d4ad4686a548871a4baec00f22": { + "9662b5e2c026477337a6a1b952ca1da98116ea3393c14ad81bf3b0e6f7705eae": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a710178c1d9213803a5ff5f56146c93908327ac97407d76c6139dfdcd9a4012f": { + "6a126c45f099dc1465fb20627b24f64632aae36028757b78b361873c454d566e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "b81af3ac1aba6361e1eb0951f2e1babd617d2218827ce4a14f2c8a5f8005b885": { + "7a331b89b73d723922e64da680ad3048d381db62fd6f9139eb9e57ea43569156": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "de8b6921dbff87fdf7182b8a948b9644aa4db01a9dbbda9749a0774f189746cc": { + "e152b1e1e80ca9edabd8cfd196b0084ee4728b8eb6b85c1de743705b8ec3f1c5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9a8d577fc6f24f49f804fa068b9a7170c9328c0def19a2b2312c9f59b5dfab02": { + "968e09a8af812ed8d7fc18b21f59d5b7623d5e1ae6fc8f5a141443f026b11aa7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "82077f26295c6ead94d7a7a7554ed4e143a4af711f8deb94b8a8a711d032c4f2": { + "b4b714689b3211e62d2fbde91099071f193e00c955f5a96a6ffa087b2e6bfa98": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "8bf2c56f7653627e757f8263815d7408dbcbb6fb86d71d55402e9800f185ed22": { + "a978ec7a2487b240242a05119affc348f41d0d9fbdce334665c4090c2f4ab1fd": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "cef1691308f6e520bbdbc8575683d17bc814f52af8ef42522d6a5bf0aee7e861": { + "3860419b6dc9dc69573f100ccbf51c008e45a8a9b6a62243ff02b869f4e86b47": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "8b79dc5672bf5c07c2a64b68cd885ba18db98b52639d349283ac6888477326a6": { + "a303e6fa7efedf997c87edfc2d79872c95d3e1a6c5310ccfe65d0826f700ab55": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c0b84d2e8690addb339fd433c9cc449d6da6632ea8398d47e9bfebde442247a0": { + "41a0cf77c6270e0e6f3941ab2fa80f712b1c916d13793ddd36a99363c07ad885": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -110,14 +110,14 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } }, "evaluations": [ { - "flag": "obfuscation-numeric-float-0", + "flag": "numeric-float-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -128,7 +128,7 @@ } }, { - "flag": "obfuscation-numeric-float-1", + "flag": "numeric-float-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -139,7 +139,7 @@ } }, { - "flag": "obfuscation-numeric-float-2", + "flag": "numeric-float-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -150,7 +150,7 @@ } }, { - "flag": "obfuscation-numeric-float-3", + "flag": "numeric-float-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -161,7 +161,7 @@ } }, { - "flag": "obfuscation-numeric-float-4", + "flag": "numeric-float-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -172,7 +172,7 @@ } }, { - "flag": "obfuscation-numeric-float-5", + "flag": "numeric-float-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -183,7 +183,7 @@ } }, { - "flag": "obfuscation-numeric-float-6", + "flag": "numeric-float-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -212,7 +212,7 @@ ] }, { - "flag": "obfuscation-numeric-float-7", + "flag": "numeric-float-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -223,7 +223,7 @@ } }, { - "flag": "obfuscation-numeric-float-8", + "flag": "numeric-float-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -252,7 +252,7 @@ ] }, { - "flag": "obfuscation-numeric-float-9", + "flag": "numeric-float-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -263,7 +263,7 @@ } }, { - "flag": "obfuscation-numeric-float-10", + "flag": "numeric-float-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -292,7 +292,7 @@ ] }, { - "flag": "obfuscation-numeric-float-11", + "flag": "numeric-float-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -309,55 +309,55 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-0" + "key": "numeric-float-0" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-1" + "key": "numeric-float-1" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-2" + "key": "numeric-float-2" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-3" + "key": "numeric-float-3" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-4" + "key": "numeric-float-4" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-5" + "key": "numeric-float-5" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-7" + "key": "numeric-float-7" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-9" + "key": "numeric-float-9" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-11" + "key": "numeric-float-11" } } ], @@ -365,49 +365,49 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-0" + "key": "numeric-float-0" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-1" + "key": "numeric-float-1" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-2" + "key": "numeric-float-2" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-3" + "key": "numeric-float-3" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-4" + "key": "numeric-float-4" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-5" + "key": "numeric-float-5" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-6" + "key": "numeric-float-6" }, "evaluation_count": 1, "_include": [ @@ -433,14 +433,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-7" + "key": "numeric-float-7" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-8" + "key": "numeric-float-8" }, "evaluation_count": 1, "_include": [ @@ -466,14 +466,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-9" + "key": "numeric-float-9" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-10" + "key": "numeric-float-10" }, "evaluation_count": 1, "_include": [ @@ -499,7 +499,7 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-11" + "key": "numeric-float-11" }, "evaluation_count": 1 } @@ -508,15 +508,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json similarity index 79% rename from precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json rename to precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json index 4b638c49..b1ec8e61 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-integer.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-integer", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "72101f81c2d474893bb90b2e9fbece5750b83659171b75d423eba3c8caa09af8": { + "6f6faa00700939aa0c395e059d8dea1cc48b2c5a20eb2ef5bc7abfeb551e2d6a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "158853f6fec44bdd14c7dcf732538d1932393333da0d38e5b1cf389febab173a": { + "6418f8b5f4df9814bfd25f30a8ef6865929075706f260ca4f48f5334f5541c89": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5e716e405677ef15a943d1e3ec8577a5436ad2b08b619801fa115e3f6056e7e1": { + "0f2ba6c096112e6b701607e345fc3a06a2a0d93710715cea905546b69b536ec0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "55aa24315699bb2e6b22e1e60dbc59588628d4987393d1709e63283f740da782": { + "ddd11d6e8fd3dfc9734f307630745484bb411a3278c36ad0dd7c681f91edcde5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "11dbbd2cc435f0d8521f492798f5cdeda39b44612afb5b2e8dbbca244eac8afb": { + "b1108b8a690b2ca3dd55e74294ed8bfdf1496b88ed4b3cb87abf27199c6b819e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5f600bc0a426abe99c1d0f650b9b8eb243179105c4a73a59f696b99967088fcd": { + "01ede755b925df7f56ae33a0bcc4d822a0495b602eea740c5b8570c025527afa": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "422fd4f38b4009a3443ac2334e4687d21e2eb02d7614a3eec83c7630585d7f4e": { + "fc310ab0e0bd16d08c9b769db925a177fa89b3f539d75b01ef8dd319c427fccf": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "77e8ec1779ffe7a8edd77b8c84358cae99993a9898b4c0cadf2336a88ae99ccd": { + "ad9ca46d7714846bd2a813e74d2af97e1cd25df3b0242f1575db2083f6f9d8b1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6779f3038ba17b00bc22bd42f0fb7078c7a3cf7843adc7884e269ad0b33a0180": { + "35eb2ca6e144c429db174c281e9e55d2b9b4e9d819a159203bbdf577354e4033": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "7224ebe3cb4e571ec923f2624b68bd3e183c199258e5523e6456b62e9254f8e5": { + "27bc83275f0f9a5e871d2e4c5af47829d9f7b72c3e5f1f224fbafe3cef986c81": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9483e1046878fedc712a9345a3e8406df5b24d42f7e0d4500ff8cc9fb1c9a161": { + "19415991350c00c660f9d74c206db9ec7d9fbde7c8a8cbdcfd6775911e40be6b": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e3e9c957b3953d38da8ada7fadcb6bea2ce78691d7cc34dbc2c75297ee4c5a95": { + "a556037a239ae8b7c3789f59013e015ebf29686de47bce8aa1bb17f99569a4e8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -110,14 +110,14 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } }, "evaluations": [ { - "flag": "obfuscation-numeric-integer-0", + "flag": "numeric-integer-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -128,7 +128,7 @@ } }, { - "flag": "obfuscation-numeric-integer-1", + "flag": "numeric-integer-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -139,7 +139,7 @@ } }, { - "flag": "obfuscation-numeric-integer-2", + "flag": "numeric-integer-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -150,7 +150,7 @@ } }, { - "flag": "obfuscation-numeric-integer-3", + "flag": "numeric-integer-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -161,7 +161,7 @@ } }, { - "flag": "obfuscation-numeric-integer-4", + "flag": "numeric-integer-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -172,7 +172,7 @@ } }, { - "flag": "obfuscation-numeric-integer-5", + "flag": "numeric-integer-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -183,7 +183,7 @@ } }, { - "flag": "obfuscation-numeric-integer-6", + "flag": "numeric-integer-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -212,7 +212,7 @@ ] }, { - "flag": "obfuscation-numeric-integer-7", + "flag": "numeric-integer-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -223,7 +223,7 @@ } }, { - "flag": "obfuscation-numeric-integer-8", + "flag": "numeric-integer-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -252,7 +252,7 @@ ] }, { - "flag": "obfuscation-numeric-integer-9", + "flag": "numeric-integer-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -263,7 +263,7 @@ } }, { - "flag": "obfuscation-numeric-integer-10", + "flag": "numeric-integer-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -292,7 +292,7 @@ ] }, { - "flag": "obfuscation-numeric-integer-11", + "flag": "numeric-integer-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -309,55 +309,55 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-0" + "key": "numeric-integer-0" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-1" + "key": "numeric-integer-1" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-2" + "key": "numeric-integer-2" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-3" + "key": "numeric-integer-3" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-4" + "key": "numeric-integer-4" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-5" + "key": "numeric-integer-5" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-7" + "key": "numeric-integer-7" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-9" + "key": "numeric-integer-9" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-11" + "key": "numeric-integer-11" } } ], @@ -365,49 +365,49 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-0" + "key": "numeric-integer-0" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-1" + "key": "numeric-integer-1" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-2" + "key": "numeric-integer-2" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-3" + "key": "numeric-integer-3" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-4" + "key": "numeric-integer-4" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-5" + "key": "numeric-integer-5" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-6" + "key": "numeric-integer-6" }, "evaluation_count": 1, "_include": [ @@ -433,14 +433,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-7" + "key": "numeric-integer-7" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-8" + "key": "numeric-integer-8" }, "evaluation_count": 1, "_include": [ @@ -466,14 +466,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-9" + "key": "numeric-integer-9" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-10" + "key": "numeric-integer-10" }, "evaluation_count": 1, "_include": [ @@ -499,7 +499,7 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-11" + "key": "numeric-integer-11" }, "evaluation_count": 1 } @@ -508,15 +508,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json similarity index 79% rename from precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json rename to precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json index 92530649..7df21eac 100644 --- a/precomputed-assignments/cases/obfuscation/obfuscation-numeric-number.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-number", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "5d1d3c167fda7f3ecadb68da40228431ed0b74ec08597fc57398fbf7f253f0d6": { + "5a1d7afff3fd522b50e8891f7af14267bdd253a1455ed12bc92188f78e12033d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4dcb0659aaeb33eb329143c5ed994c52800521968edafd58c95c50377873ccc4": { + "cbc15a556f7e611fa26f06f6a6334ee91ad5d6a344496452557db41ed992bc25": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "8023572892cfed9fb5ee411c451eab7480329f32331f3845e76234275bede04d": { + "f1c431305b153a3030fadebc0057e634b6162cd6acda9744a763082b1197c740": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "d15970ae43ef67b0a9844f04cd8680567400e12340d545aa608a0cc9b89bed95": { + "7b3cbab857c42d32035705aa891dd7a2fbadcbe1fc0f1e28f3611076a6aa7945": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4373eda204ffa94cc5c15d29bd5a33b828033f4d2382f708801a7ea5c298b286": { + "acdfe2ec44b3eb617b86da9a49166e3abecbc85c00951e548516cfd748056403": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5a95ab1a979318a8d113fcdb44a9a5262f0f99a6fb58e0f782b1ac5dbc88f1ef": { + "3af8ba8e93c58c248ed0d943b8bfbc630236bbbeca96be121efc46ed8da4b9b0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "dceef8e665186596db59e1c8bf2887adf20969530b48091e1bcf3517bf804a7b": { + "efb49302883b084e49d51cb2f41fd795224a571d182536e203f6ab28e6648b77": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6b8f85c278e445167be847d3a94a91c1865221df7cc63dc8f31aacbb9454e430": { + "c38a503ec9a1c032f0a073e23d6d0ede2eccf245aac33f9db02a5374728c810e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "81da1069b7cb802d039c7cd27d4c0f4db2515a5c64dd411a522d6f300592c31c": { + "499aef4e9d9d7df120a79816e9406b5925664e851fd638a129f525588e8a9385": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "0a2bcee9cec623b02ca219d7b3f15415767bb630f6bcf9af75fda36c2a689f64": { + "b004cb3fb7dbb1846ba17f461b1a0931cd52d9ffde2c5419d51c0b47e42baf99": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "be4ecda1ccdb165dc68fcffc30f021c7e83a88b8a2283784498ac761385f26d0": { + "d858ab64de6a97983f95792030011b2dd635938aaefd90fee12b37ef67e9dac3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c4f3ba7ee684fddd93335901ae9b0af3959704d06b5a028449b08854d5384e3d": { + "3ece2688265c5c214d2904619cf0294042830dc10d72dae32e50050c7cded8c3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -110,14 +110,14 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } }, "evaluations": [ { - "flag": "obfuscation-numeric-number-0", + "flag": "numeric-number-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -128,7 +128,7 @@ } }, { - "flag": "obfuscation-numeric-number-1", + "flag": "numeric-number-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -139,7 +139,7 @@ } }, { - "flag": "obfuscation-numeric-number-2", + "flag": "numeric-number-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -150,7 +150,7 @@ } }, { - "flag": "obfuscation-numeric-number-3", + "flag": "numeric-number-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -161,7 +161,7 @@ } }, { - "flag": "obfuscation-numeric-number-4", + "flag": "numeric-number-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -172,7 +172,7 @@ } }, { - "flag": "obfuscation-numeric-number-5", + "flag": "numeric-number-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -183,7 +183,7 @@ } }, { - "flag": "obfuscation-numeric-number-6", + "flag": "numeric-number-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -212,7 +212,7 @@ ] }, { - "flag": "obfuscation-numeric-number-7", + "flag": "numeric-number-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -223,7 +223,7 @@ } }, { - "flag": "obfuscation-numeric-number-8", + "flag": "numeric-number-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -252,7 +252,7 @@ ] }, { - "flag": "obfuscation-numeric-number-9", + "flag": "numeric-number-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -263,7 +263,7 @@ } }, { - "flag": "obfuscation-numeric-number-10", + "flag": "numeric-number-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -292,7 +292,7 @@ ] }, { - "flag": "obfuscation-numeric-number-11", + "flag": "numeric-number-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -309,55 +309,55 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-0" + "key": "numeric-number-0" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-1" + "key": "numeric-number-1" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-2" + "key": "numeric-number-2" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-3" + "key": "numeric-number-3" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-4" + "key": "numeric-number-4" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-5" + "key": "numeric-number-5" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-7" + "key": "numeric-number-7" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-9" + "key": "numeric-number-9" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-11" + "key": "numeric-number-11" } } ], @@ -365,49 +365,49 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-0" + "key": "numeric-number-0" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-1" + "key": "numeric-number-1" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-2" + "key": "numeric-number-2" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-3" + "key": "numeric-number-3" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-4" + "key": "numeric-number-4" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-5" + "key": "numeric-number-5" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-6" + "key": "numeric-number-6" }, "evaluation_count": 1, "_include": [ @@ -433,14 +433,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-7" + "key": "numeric-number-7" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-8" + "key": "numeric-number-8" }, "evaluation_count": 1, "_include": [ @@ -466,14 +466,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-9" + "key": "numeric-number-9" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-10" + "key": "numeric-number-10" }, "evaluation_count": 1, "_include": [ @@ -499,7 +499,7 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-11" + "key": "numeric-number-11" }, "evaluation_count": 1 } @@ -508,15 +508,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json new file mode 100644 index 00000000..eb4655e1 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json @@ -0,0 +1,106 @@ +{ + "name": "obfuscation-object-nested-deep", + "description": "Generated obfuscated companion: Object flag with deeply nested structure. SDK must preserve full object hierarchy without flattening or truncating.", + "context": { + "targetingKey": "nested-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "a75e90efc57ed0e56bd11c3a9b17a2623dcc2a412fa52f3ffa742139fa6b93af": { + "allocationKey": "alloc-deep", + "variationKey": "deep-config", + "variationType": "object", + "variationValue": { + "level1": { + "level2": { + "level3": { + "enabled": true, + "tags": [ + "alpha", + "beta" + ], + "threshold": 0.95 + } + }, + "name": "nested-test" + }, + "metadata": { + "version": 3, + "empty": {} + } + }, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.config.deep", + "variationType": "object", + "defaultValue": {}, + "expectedResult": { + "value": { + "level1": { + "level2": { + "level3": { + "enabled": true, + "tags": [ + "alpha", + "beta" + ], + "threshold": 0.95 + } + }, + "name": "nested-test" + }, + "metadata": { + "version": 3, + "empty": {} + } + }, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "deep-config" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], + "evaluationEvents": [ + { + "_count": 1 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json new file mode 100644 index 00000000..7862719d --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json @@ -0,0 +1,127 @@ +{ + "name": "obfuscation-special-characters-in-keys", + "description": "Generated obfuscated companion: Flag keys contain dots, hyphens, and underscores. SDK must handle these without mangling the key during lookup or emission.", + "context": { + "targetingKey": "special-key-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "5ed289266d5a488851241b3122aab121698b75a75da9fb687c671d606a099d99": { + "allocationKey": "alloc-dot", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a5bcd95f28f8cf915795582c1edd5b263d2f42cd8a513eb6d69aeef26c39ead0": { + "allocationKey": "alloc-hyphen", + "variationKey": "active", + "variationType": "string", + "variationValue": "yes", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e4fb9f7c538eac04c76c225b60d43a48b5f514a112a148d95d25754ad14cc8a8": { + "allocationKey": "alloc-underscore", + "variationKey": "v2-val", + "variationType": "integer", + "variationValue": 100, + "reason": "DEFAULT", + "doLog": true + }, + "dfd73ab3f42a47bd441de225f613432120e3eb38a6f00a21cb19a45a9de23c70": { + "allocationKey": "alloc-mixed", + "variationKey": "mixed-val", + "variationType": "float", + "variationValue": 2.718, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "com.example.feature.enabled", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "on" + } + }, + { + "flag": "my-team--feature-toggle", + "variationType": "string", + "defaultValue": "no", + "expectedResult": { + "value": "yes", + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "active" + } + }, + { + "flag": "legacy_flag_name_v2", + "variationType": "integer", + "defaultValue": 0, + "expectedResult": { + "value": 100, + "reason": "DEFAULT", + "errorCode": null, + "variantKey": "v2-val" + } + }, + { + "flag": "mix.ed-key_style.v1", + "variationType": "float", + "defaultValue": 0.0, + "expectedResult": { + "value": 2.718, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "mixed-val" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 4 + } + ], + "evaluationEvents": [ + { + "_count": 4 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json new file mode 100644 index 00000000..d7950c8e --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json @@ -0,0 +1,148 @@ +{ + "name": "obfuscation-type-mismatch-all-types", + "description": "Generated obfuscated companion: Wrong nonnumeric getters return TYPE_MISMATCH; an exact numeric conversion succeeds.", + "context": { + "targetingKey": "mismatch-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "937d54653333211a63876dd0e042edb2ef108105fb918420af28ff03f620a982": { + "allocationKey": "alloc-mm", + "variationKey": "var-bool", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ed1838c062426321f2125dd154000611b46339c11bbbab4228df3fad88007f6c": { + "allocationKey": "alloc-mm", + "variationKey": "var-str", + "variationType": "string", + "variationValue": "hello", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7ba0090b5e66bc7319950479a1e905c26fedd82e605530cdb4eb0adb4c7d5515": { + "allocationKey": "alloc-mm", + "variationKey": "var-int", + "variationType": "integer", + "variationValue": 7, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1fa87b21c5821755d962ce404e18a06c5ae2656dd33d4e915e80e425fc77c190": { + "allocationKey": "alloc-mm", + "variationKey": "var-float", + "variationType": "float", + "variationValue": 3.14, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9a172110c6d75715ead4e47f3063d7ab4d92141de6f83681141130da17f9b743": { + "allocationKey": "alloc-mm", + "variationKey": "var-obj", + "variationType": "object", + "variationValue": { + "key": "value" + }, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "fixture.mismatch.bool-as-string", + "variationType": "string", + "defaultValue": "fallback", + "expectedResult": { + "value": "fallback", + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "fixture.mismatch.string-as-int", + "variationType": "integer", + "defaultValue": -1, + "expectedResult": { + "value": -1, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "fixture.mismatch.int-as-float", + "variationType": "float", + "defaultValue": 0.0, + "expectedResult": { + "value": 7, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "var-int" + } + }, + { + "flag": "fixture.mismatch.float-as-bool", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": false, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "fixture.mismatch.object-as-string", + "variationType": "string", + "defaultValue": "none", + "expectedResult": { + "value": "none", + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], + "evaluationEvents": [ + { + "_count": 5 + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "android", + "reason": "Supports obfuscation and the source fixture." + }, + { + "platform": "flutter", + "reason": "Supports obfuscation and the source fixture." + } + ] +} diff --git a/precomputed-assignments/obfuscation-exceptions.json b/precomputed-assignments/obfuscation-exceptions.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/precomputed-assignments/obfuscation-exceptions.json @@ -0,0 +1 @@ +{} From faa89edca7043428824ef65050e3293113100624 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 19:03:15 -0500 Subject: [PATCH 11/12] FFL-3568: Cover integral floating values and snapshot refreshes --- ci/test_precomputed_sync.py | 44 +++++++++++++++++++ .../cases/evaluator/numeric-float.json | 2 +- .../cases/evaluator/numeric-integer.json | 2 +- .../cases/evaluator/numeric-number.json | 2 +- .../evaluator/obfuscation-numeric-float.json | 2 +- .../obfuscation-numeric-integer.json | 2 +- .../evaluator/obfuscation-numeric-number.json | 2 +- 7 files changed, 50 insertions(+), 6 deletions(-) diff --git a/ci/test_precomputed_sync.py b/ci/test_precomputed_sync.py index d541a0bf..738ef52c 100644 --- a/ci/test_precomputed_sync.py +++ b/ci/test_precomputed_sync.py @@ -2,7 +2,9 @@ import importlib.util import json from pathlib import Path +import shutil import subprocess +import sys import tempfile import unittest @@ -73,6 +75,48 @@ def test_unity_guids_are_stable(self): self.assertEqual(original, meta.read_text()) sync.verify(self.destination) + def test_cli_refresh_preserves_guids_and_replaces_cases(self): + consumer = self.root / 'consumer' + ci = consumer / 'ci' + ci.mkdir(parents=True) + importer = ci / 'sync-precomputed-fixtures.py' + shutil.copyfile(sync.__file__, importer) + (ci / 'precomputed-fixtures.json').write_text(json.dumps({ + 'destination': 'fixtures', 'unityMetadata': True, + })) + command = [sys.executable, str(importer), '--source', str(self.source), '--ref', 'HEAD'] + subprocess.run(command, check=True, capture_output=True) + destination = consumer / 'fixtures' + meta = destination / 'cases/new/nested/case.json.meta' + custom_guid = '0123456789abcdef0123456789abcdef' + meta.write_text(meta.read_text().replace( + meta.read_text().split('guid: ')[1].splitlines()[0], custom_guid, + )) + removed = self.source / 'precomputed-assignments/cases/removed.json' + removed.write_text(json.dumps({'name': 'removed', 'evaluations': []})) + subprocess.run(['git', '-C', self.source, 'add', '.'], check=True) + commit = ['git', '-C', self.source, '-c', 'commit.gpgsign=false', '-c', + 'core.hooksPath=/dev/null', '-c', 'user.name=Fixture test', '-c', + 'user.email=fixture@example.invalid', 'commit', '-qm', 'fixture update'] + subprocess.run(commit, check=True) + subprocess.run(command, check=True, capture_output=True) + self.assertTrue((destination / 'cases/removed.json.meta').exists()) + removed.unlink() + changed = self.source / 'precomputed-assignments/cases/new/nested/case.json' + changed.write_text(json.dumps({'name': 'case', 'evaluations': [], 'description': 'Updated'})) + added = changed.with_name('added.json') + added.write_text(json.dumps({'name': 'added', 'evaluations': []})) + subprocess.run(['git', '-C', self.source, 'add', '.'], check=True) + subprocess.run(commit, check=True) + subprocess.run(command, check=True, capture_output=True) + self.assertIn('guid: ' + custom_guid, meta.read_text()) + self.assertEqual(changed.read_bytes(), (destination / 'cases/new/nested/case.json').read_bytes()) + self.assertFalse((destination / 'cases/removed.json').exists()) + self.assertFalse((destination / 'cases/removed.json.meta').exists()) + self.assertTrue((destination / 'cases/new/nested/added.json.meta').exists()) + subprocess.run(command + ['--verify'], check=True, capture_output=True) + subprocess.run(command + ['--check'], check=True, capture_output=True) + if __name__ == '__main__': unittest.main() diff --git a/precomputed-assignments/cases/evaluator/numeric-float.json b/precomputed-assignments/cases/evaluator/numeric-float.json index dd8b3cb8..43e5f876 100644 --- a/precomputed-assignments/cases/evaluator/numeric-float.json +++ b/precomputed-assignments/cases/evaluator/numeric-float.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/precomputed-assignments/cases/evaluator/numeric-integer.json b/precomputed-assignments/cases/evaluator/numeric-integer.json index e1ce0740..2b4e63cf 100644 --- a/precomputed-assignments/cases/evaluator/numeric-integer.json +++ b/precomputed-assignments/cases/evaluator/numeric-integer.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/precomputed-assignments/cases/evaluator/numeric-number.json b/precomputed-assignments/cases/evaluator/numeric-number.json index c97208a8..498ff4af 100644 --- a/precomputed-assignments/cases/evaluator/numeric-number.json +++ b/precomputed-assignments/cases/evaluator/numeric-number.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json index cb5d7f40..bf65bb4b 100644 --- a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json index b1ec8e61..e61afa1d 100644 --- a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json index 7df21eac..6ea6bdf9 100644 --- a/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, From 7593ccd54e13eaa96236cd7a8d57644209e48ebe Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Wed, 4 Nov 2026 16:17:11 +0000 Subject: [PATCH 12/12] FFL-3568: Preview mobile fixture compatibility in test-data CI --- .github/workflows/mobile-compatibility.yml | 136 ++++++++++++++++ ci/mobile_conformance.py | 179 +++++++++++++++++++++ ci/test_mobile_conformance.py | 136 ++++++++++++++++ precomputed-assignments/README.md | 24 +++ 4 files changed, 475 insertions(+) create mode 100644 .github/workflows/mobile-compatibility.yml create mode 100644 ci/mobile_conformance.py create mode 100644 ci/test_mobile_conformance.py diff --git a/.github/workflows/mobile-compatibility.yml b/.github/workflows/mobile-compatibility.yml new file mode 100644 index 00000000..e48a40a5 --- /dev/null +++ b/.github/workflows/mobile-compatibility.yml @@ -0,0 +1,136 @@ +name: Mobile compatibility preview + +on: + pull_request: + paths: + - .github/workflows/mobile-compatibility.yml + - ci/mobile_conformance.py + - ci/test_mobile_conformance.py + - ci/sync-precomputed-fixtures.py + - ci/precomputed_obfuscation.py + - schemas/precomputed-assignment.schema.json + - precomputed-assignments/** + +permissions: + contents: read + +concurrency: + group: mobile-compatibility-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + tooling: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: python3 -m unittest discover -s ci -p 'test_mobile_conformance.py' + + mobile: + name: ${{ matrix.platform }} unit compatibility (advisory) + runs-on: ${{ matrix.runner }} + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + include: + # Temporary development branches. Switch each ref to develop after the + # canonical runner lands; the checked-out SDK SHA stays fixed per run. + - platform: ios + runner: macos-26 + repository: DataDog/dd-sdk-ios + ref: leo.romanovsky/precompute-fixture-fanout + - platform: android + runner: ubuntu-latest + repository: DataDog/dd-sdk-android + ref: leo.romanovsky/ffl-3568/precompute-fixture-fanout + steps: + - name: Check out fixture PR + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + path: fixtures + persist-credentials: false + - name: Check out SDK once for both fixture revisions + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: ${{ matrix.repository }} + ref: ${{ matrix.ref }} + path: downstream + persist-credentials: false + - name: Record revisions + id: revisions + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + SDK_REPOSITORY: ${{ matrix.repository }} + SDK_REF: ${{ matrix.ref }} + run: | + mkdir -p "$RUNNER_TEMP/mobile-conformance" + base=$(git -C fixtures merge-base "$BASE_SHA" "$HEAD_SHA") + echo "base=$base" >> "$GITHUB_OUTPUT" + { + echo "SDK: $SDK_REPOSITORY @ $SDK_REF" + git -C downstream rev-parse HEAD + echo "Base fixtures: $base" + echo "Proposed fixtures: $HEAD_SHA" + } | tee "$RUNNER_TEMP/mobile-conformance/revisions.txt" >> "$GITHUB_STEP_SUMMARY" + - name: Set up Java + if: matrix.platform == 'android' + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + distribution: temurin + java-version: '21' + - name: Prepare Android build tools + if: matrix.platform == 'android' + run: | + set -o pipefail + { + java -version + "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \ + 'platforms;android-36' 'build-tools;36.0.0' + } 2>&1 | tee "$RUNNER_TEMP/mobile-conformance/setup.log" + - name: Set up Ruby for iOS repository setup + if: matrix.platform == 'ios' + uses: ruby/setup-ruby@a0102e0972be65f351c307e2d64b9314a57c8073 # v1.324.0 + with: + ruby-version: '3.3' + - name: Prepare iOS dependencies + if: matrix.platform == 'ios' + working-directory: downstream + env: + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer + run: | + set -o pipefail + { + xcodebuild -version + xcrun simctl list devices available + make repo-setup ENV=ci + # Public dependencies; the SDK's GitLab STS wrapper is not needed here. + carthage bootstrap --platform iOS --use-xcframeworks + } 2>&1 | tee "$RUNNER_TEMP/mobile-conformance/setup.log" + - name: Compare native unit-test results + id: comparison + env: + PLATFORM: ${{ matrix.platform }} + BASE_SHA: ${{ steps.revisions.outputs.base }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer + IOS_DESTINATION: platform=iOS Simulator,name=iPhone 17 Pro,OS=26.5 + run: | + python3 fixtures/ci/mobile_conformance.py \ + --platform "$PLATFORM" --fixtures "$GITHUB_WORKSPACE/fixtures" \ + --sdk "$GITHUB_WORKSPACE/downstream" --base "$BASE_SHA" --head "$HEAD_SHA" \ + --output "$RUNNER_TEMP/mobile-conformance" + - name: Report unavailable preview + if: failure() && steps.comparison.outcome != 'success' + run: | + echo 'Mobile preview incomplete: inspect setup/build logs. This is not evidence of fixture compatibility.' >> "$GITHUB_STEP_SUMMARY" + - name: Upload native results and logs + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: mobile-compatibility-${{ matrix.platform }} + path: ${{ runner.temp }}/mobile-conformance + if-no-files-found: warn diff --git a/ci/mobile_conformance.py b/ci/mobile_conformance.py new file mode 100644 index 00000000..c8dbc7e4 --- /dev/null +++ b/ci/mobile_conformance.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Unless explicitly stated otherwise, all files in this repository are licensed under the +# Apache License Version 2.0. This product includes software developed at Datadog +# (https://www.datadoghq.com/). Copyright 2025-Present Datadog, Inc. +"""Preview base/head precomputed fixtures against one disposable native SDK checkout.""" +import argparse +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess +import xml.etree.ElementTree as ET + +spec = importlib.util.spec_from_file_location( + 'snapshot_importer', Path(__file__).with_name('sync-precomputed-fixtures.py')) +importer = importlib.util.module_from_spec(spec) +spec.loader.exec_module(importer) + +DESTINATIONS = { + 'ios': 'DatadogFlags/Tests/Fixtures/precomputed', + 'android': 'reliability/single-fit/flags/src/test/resources/precomputed', +} +ANDROID_MODULE = 'reliability/single-fit/flags' +ANDROID_TEST = 'com.datadog.android.flags.integration.CanonicalPrecomputedTest' + + +def install_snapshot(fixtures, sha, sdk, platform): + # Reuse the byte-preserving importer, including for bases predating its CLI. + # Never accept a configurable replacement destination in a downstream repo. + content = importer.snapshot(fixtures, sha) + destination = sdk / DESTINATIONS[platform] + if destination.resolve() != destination or any(p.is_symlink() for p in destination.parents): + raise ValueError('Snapshot destination must not contain symlinks') + if destination.exists(): + if not (destination / 'SOURCE.json').is_file(): + raise ValueError('Refusing to replace a directory without snapshot provenance') + shutil.rmtree(destination) + destination.mkdir(parents=True) + for name, data in content.items(): + path = destination / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + return len(json.loads(content['index.json'])) + + +def native_command(platform, output): + if platform == 'android': + return ['./gradlew', ':reliability:single-fit:flags:cleanTestDebugUnitTest', + ':reliability:single-fit:flags:testDebugUnitTest', '--tests', ANDROID_TEST, + '--no-daemon', '--no-build-cache', '--console=plain'] + return ['xcodebuild', '-workspace', 'Datadog.xcworkspace', '-scheme', 'DatadogFlags', + '-destination', os.environ['IOS_DESTINATION'], + '-only-testing:DatadogFlagsTests/CanonicalPrecomputedTests', + '-resultBundlePath', str(output / 'tests.xcresult'), + 'CODE_SIGNING_ALLOWED=NO', 'test'] + + +def android_results(directory): + cases = [] + for file in directory.glob('TEST-*.xml'): + cases.extend(ET.parse(file).getroot().iter('testcase')) + # Require actual canonical cases, not an unrelated successful test report. + cases = [c for c in cases if c.get('classname') == ANDROID_TEST] + executed = [c for c in cases if c.find('skipped') is None] + failures = [c for c in executed if c.find('failure') is not None or c.find('error') is not None] + return {'executed': len(executed), 'failed': len(failures), + 'failures': [c.get('name') for c in failures]} + + +def ios_results(output): + raw = subprocess.check_output(['xcrun', 'xcresulttool', 'get', 'test-results', 'summary', + '--path', str(output / 'tests.xcresult')]) + (output / 'test-summary.json').write_bytes(raw) + result = json.loads(raw) + return {'executed': result['passedTests'] + result['failedTests'], + 'failed': result['failedTests'], 'failures': result.get('testFailures', [])} + + +def test_status(exit_code, report): + if report['executed'] == 0: + return 'no-tests' if exit_code == 0 else 'build-or-runner-failure' + if report['failed']: + return 'test-failure' + return 'pass' if exit_code == 0 else 'build-or-runner-failure' + + +def compare(base, head): + if base == head == 'pass': + return 'compatible' + if base == 'pass' and head == 'test-failure': + return 'new-regression' + if base == 'test-failure' and head == 'pass': + return 'improvement' + if base == head == 'test-failure': + return 'inconclusive-both-fail' + return 'inconclusive-setup-or-execution' + + +def run_revision(args, label, sha): + output = args.output / label + output.mkdir() # Never reuse stale results from an earlier attempt. + result = {'fixture_sha': sha, 'status': 'setup-failure'} + try: + result['fixture_count'] = install_snapshot(args.fixtures, sha, args.sdk, args.platform) + command = native_command(args.platform, output) + result['command'] = command + if args.platform == 'android': + # A compilation failure must not reuse the base run's JUnit report. + reports = args.sdk / ANDROID_MODULE / 'build/test-results/testDebugUnitTest' + if reports.exists(): + shutil.rmtree(reports) + env = {**os.environ, 'SKIP_LINT': '1', 'USE_TEST_VISIBILITY': '0'} + with (output / 'test.log').open('w') as log: + result['exit_code'] = subprocess.run(command, cwd=args.sdk, env=env, + stdout=log, stderr=subprocess.STDOUT).returncode + result['status'] = 'build-or-runner-failure' + if args.platform == 'android': + reports = args.sdk / ANDROID_MODULE / 'build/test-results/testDebugUnitTest' + if reports.exists(): + shutil.copytree(reports, output / 'junit') + report = android_results(output / 'junit') + else: + report = ios_results(output) + result.update(report) + result['status'] = test_status(result['exit_code'], report) + except (OSError, ValueError, KeyError, ET.ParseError, subprocess.CalledProcessError) as error: + result['error'] = str(error) + (output / 'result.json').write_text(json.dumps(result, indent=2) + '\n') + print(f'{label}: {result["status"]}', flush=True) + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--platform', choices=DESTINATIONS, required=True) + for name in ('fixtures', 'sdk', 'output'): + parser.add_argument('--' + name, type=Path, required=True) + parser.add_argument('--base', required=True) + parser.add_argument('--head', required=True) + args = parser.parse_args() + args.fixtures, args.sdk, args.output = (p.resolve() for p in (args.fixtures, args.sdk, args.output)) + args.output.mkdir(parents=True, exist_ok=True) + sdk_sha = importer.git(args.sdk, 'rev-parse', 'HEAD').decode().strip() + # Resolve immutable revisions up front. The SDK checkout never changes between runs. + base_sha, head_sha = [importer.git(args.fixtures, 'rev-parse', '--verify', s + '^{commit}') + .decode().strip() for s in (args.base, args.head)] + base = run_revision(args, 'base', base_sha) + head = run_revision(args, 'head', head_sha) + classification = compare(base['status'], head['status']) + result = {'platform': args.platform, 'sdk_sha': sdk_sha, + 'classification': classification, 'base': base, 'head': head} + (args.output / 'comparison.json').write_text(json.dumps(result, indent=2) + '\n') + summary = (f'### {args.platform} unit compatibility: {classification}\n\n' + f'SDK commit: `{sdk_sha}`\n\n' + '| Fixtures | Revision | Result | Executed tests |\n' + '| --- | --- | --- | ---: |\n' + f'| Base | `{base_sha}` | {base["status"]} | {base.get("executed", "unknown")} |\n' + f'| Proposed | `{head_sha}` | {head["status"]} | {head.get("executed", "unknown")} |\n\n' + 'Advisory unit-test evidence, not deployed-client validation. ' + 'Both failing does not establish that the failures are identical. ' + 'Inspect the per-revision native reports and logs.\n') + for label, revision in [('Base', base), ('Proposed', head)]: + if revision.get('failures'): + # JSON escaping prevents test names from injecting Markdown/HTML. + details = json.dumps(revision['failures'][:20], indent=2).replace('`', '\\u0060').replace('<', '\\u003c') + summary += f'\n{label} failures (first 20):\n\n```json\n{details}\n```\n' + print(summary) + if os.environ.get('GITHUB_STEP_SUMMARY'): + with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as file: + file.write(summary) + if classification != 'compatible': + print(f'::warning title=Mobile fixture preview::{args.platform}: {classification}; see artifacts.') + # Completed comparisons are advisory. Setup/execution gaps must stay visibly failed. + return 1 if classification == 'inconclusive-setup-or-execution' else 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/ci/test_mobile_conformance.py b/ci/test_mobile_conformance.py new file mode 100644 index 00000000..12bd9dea --- /dev/null +++ b/ci/test_mobile_conformance.py @@ -0,0 +1,136 @@ +# Unless explicitly stated otherwise, all files in this repository are licensed under the +# Apache License Version 2.0. This product includes software developed at Datadog +# (https://www.datadoghq.com/). Copyright 2025-Present Datadog, Inc. +"""Exercise comparison reporting and snapshot isolation without native toolchains.""" +import argparse +import contextlib +import io +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +import mobile_conformance as preview + + +class PreviewTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name).resolve() + self.sdk = self.root / 'sdk' + self.sdk.mkdir() + self.args = argparse.Namespace(platform='android', sdk=self.sdk, fixtures=self.root, + output=self.root / 'output') + self.args.output.mkdir() + + def test_comparison_classification(self): + for base, head, expected in [ + ('pass', 'pass', 'compatible'), + ('pass', 'test-failure', 'new-regression'), + ('test-failure', 'pass', 'improvement'), + ('test-failure', 'test-failure', 'inconclusive-both-fail'), + ('pass', 'build-or-runner-failure', 'inconclusive-setup-or-execution'), + ('setup-failure', 'pass', 'inconclusive-setup-or-execution'), + ('pass', 'no-tests', 'inconclusive-setup-or-execution'), + ]: + with self.subTest(base=base, head=head): + self.assertEqual(preview.compare(base, head), expected) + + def test_only_executed_canonical_tests_count(self): + (self.root / 'TEST-example.xml').write_text(f''' + + + + + ''') + report = preview.android_results(self.root) + self.assertEqual(report, {'executed': 2, 'failed': 1, 'failures': ['fails']}) + self.assertEqual(preview.test_status(1, report), 'test-failure') + + def test_success_exit_with_no_tests_is_not_compatible(self): + self.assertEqual(preview.test_status(0, {'executed': 0, 'failed': 0}), 'no-tests') + self.assertEqual(preview.test_status(1, {'executed': 1, 'failed': 0}), 'build-or-runner-failure') + + def test_swift_native_summary(self): + summary = {'passedTests': 0, 'failedTests': 1, 'skippedTests': 2, + 'testFailures': [{'message': 'fractional value accepted'}]} + with patch.object(preview.subprocess, 'check_output', return_value=json.dumps(summary).encode()): + self.assertEqual(preview.ios_results(self.root)['executed'], 1) + self.assertEqual(json.loads((self.root / 'test-summary.json').read_text()), summary) + + def test_snapshot_copies_exact_bytes_and_removes_old_cases(self): + # Use the actual committed fixture tree, without creating unsigned test commits. + fixtures = Path(__file__).resolve().parent.parent + destination = self.sdk / preview.DESTINATIONS['android'] + destination.mkdir(parents=True) + (destination / 'SOURCE.json').write_text('{}') + (destination / 'obsolete.json').write_text('{}') + (self.sdk / 'keep.txt').write_text('unrelated SDK content') + count = preview.install_snapshot(fixtures, 'HEAD', self.sdk, 'android') + self.assertGreater(count, 0) + self.assertFalse((destination / 'obsolete.json').exists()) + self.assertEqual((self.sdk / 'keep.txt').read_text(), 'unrelated SDK content') + preview.importer.verify(destination) + for name, value in preview.importer.snapshot(fixtures, 'HEAD').items(): + self.assertEqual((destination / name).read_bytes(), value) + + def test_refuses_unowned_or_symlink_destination(self): + destination = self.sdk / preview.DESTINATIONS['android'] + destination.mkdir(parents=True) + (destination / 'keep.txt').write_text('keep') + with patch.object(preview.importer, 'snapshot', return_value={}): + with self.assertRaises(ValueError): + preview.install_snapshot(self.root, 'HEAD', self.sdk, 'android') + destination.rename(destination.with_name('real')) + destination.symlink_to(destination.with_name('real'), target_is_directory=True) + with self.assertRaises(ValueError): + preview.install_snapshot(self.root, 'HEAD', self.sdk, 'android') + self.assertEqual((destination / 'keep.txt').read_text(), 'keep') + + def test_snapshot_installation_does_not_round_numbers(self): + raw = b'{"value": 4503599627370497.5, "decimal": 1.20000000000000001}\n' + content = {'index.json': b'["cases/numbers.json"]', 'cases/numbers.json': raw} + with patch.object(preview.importer, 'snapshot', return_value=content): + preview.install_snapshot(self.root, 'HEAD', self.sdk, 'ios') + self.assertEqual((self.sdk / preview.DESTINATIONS['ios'] / 'cases/numbers.json').read_bytes(), raw) + + def test_build_failure_cannot_reuse_base_test_results(self): + reports = self.sdk / preview.ANDROID_MODULE / 'build/test-results/testDebugUnitTest' + reports.mkdir(parents=True) + (reports / 'TEST-stale.xml').write_text(f'') + with patch.object(preview, 'install_snapshot', return_value=57), \ + patch.object(preview.subprocess, 'run', return_value=argparse.Namespace(returncode=1)): + result = preview.run_revision(self.args, 'head', 'a' * 40) + self.assertNotEqual(result['status'], 'pass') + self.assertEqual(result['executed'], 0) + self.assertFalse(reports.exists()) + + def test_setup_failure_is_recorded_without_starting_tests(self): + with patch.object(preview, 'install_snapshot', side_effect=ValueError('missing corpus')), \ + patch.object(preview.subprocess, 'run') as run: + result = preview.run_revision(self.args, 'base', 'b' * 40) + run.assert_not_called() + self.assertEqual(result['status'], 'setup-failure') + self.assertEqual(json.loads((self.args.output / 'base/result.json').read_text()), result) + + def test_comparison_runs_head_after_base_failure_and_records_fixed_sdk(self): + argv = ['preview', '--platform', 'android', '--fixtures', str(self.root), + '--sdk', str(self.sdk), '--output', str(self.args.output), + '--base', 'base', '--head', 'head'] + with patch('sys.argv', argv), \ + patch.object(preview.importer, 'git', side_effect=[b'a' * 40, b'b' * 40, b'c' * 40]), \ + patch.object(preview, 'run_revision', side_effect=[ + {'status': 'test-failure'}, {'status': 'test-failure'}]) as run, \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(preview.main(), 0) # Completed mismatch remains advisory. + self.assertEqual([call.args[1:] for call in run.call_args_list], + [('base', 'b' * 40), ('head', 'c' * 40)]) + result = json.loads((self.args.output / 'comparison.json').read_text()) + self.assertEqual(result['sdk_sha'], 'a' * 40) + self.assertEqual(result['classification'], 'inconclusive-both-fail') + + +if __name__ == '__main__': + unittest.main() diff --git a/precomputed-assignments/README.md b/precomputed-assignments/README.md index 380812a7..c6bb8607 100644 --- a/precomputed-assignments/README.md +++ b/precomputed-assignments/README.md @@ -249,6 +249,30 @@ those known digests, including Unicode, whitespace, and embedded NUL keys. ## Snapshot maintenance +### Preview client compatibility in fixture PRs + +The [mobile compatibility workflow](../.github/workflows/mobile-compatibility.yml) +runs the iOS canonical XCTest suite on an iOS Simulator and the Android canonical +JVM unit suite on Linux. Each job checks out its SDK once, records the resolved +commit, and runs that same code against the fixture merge base and proposed head. +Snapshots are imported into disposable CI checkouts; SDK repositories are not +updated or published. The SDK refs temporarily select the development branches +containing the canonical runners. After those land, change the workflow refs to +the SDKs' default branches (`develop`). + +The PR's Actions summaries show both fixture revisions, the SDK commit, test +counts, and failures. Artifacts contain separate logs, JUnit XML or Xcode result +bundles, and `comparison.json`. Test mismatches are advisory. Missing tests, +snapshot failures, and build/runner failures make the preview incomplete and are +reported separately. When both revisions fail, the result is inconclusive, not +proof that the proposed fixtures introduce no additional failures. + +These jobs run the checked-out SDK runners unchanged. Their coverage and numeric +parsing limitations still apply: a green run does not prove preservation of raw +numeric literals, native-device behavior, or deployed-backend compatibility. + +### Refresh consumer snapshots + Consumers can copy `ci/sync-precomputed-fixtures.py` and configure the destination in `ci/precomputed-fixtures.json`. The importer copies committed source bytes, records the full SHA and file digests in `SOURCE.json`, generates `index.json`,