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/.github/workflows/validate-precomputed-fixtures.yml b/.github/workflows/validate-precomputed-fixtures.yml index a232a580..d8b68825 100644 --- a/.github/workflows/validate-precomputed-fixtures.yml +++ b/.github/workflows/validate-precomputed-fixtures.yml @@ -5,6 +5,9 @@ on: paths: - .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/** @@ -32,3 +35,9 @@ 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 f7bccf06..9ea8353c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -140,14 +140,14 @@ 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 | 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 | @@ -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/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/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/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py new file mode 100755 index 00000000..e6ecb047 --- /dev/null +++ b/ci/sync-precomputed-fixtures.py @@ -0,0 +1,178 @@ +#!/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 +import json +import os +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/' +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 TypeError('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 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 + 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') + 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: + 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) + 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'])) + 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_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/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/ci/test_precomputed_sync.py b/ci/test_precomputed_sync.py new file mode 100644 index 00000000..738ef52c --- /dev/null +++ b/ci/test_precomputed_sync.py @@ -0,0 +1,122 @@ +"""Regression tests for generated fixture snapshots and recursive discovery.""" +import importlib.util +import json +from pathlib import Path +import shutil +import subprocess +import sys +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) + + 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/ci/test_precomputed_validation.py b/ci/test_precomputed_validation.py new file mode 100644 index 00000000..b2eb3862 --- /dev/null +++ b/ci/test_precomputed_validation.py @@ -0,0 +1,71 @@ +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_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) + 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..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,11 +137,13 @@ 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}") # 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 +183,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..c6bb8607 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,118 @@ Validate fixtures locally with: ```bash python3 ci/validate-precomputed-fixtures.py ``` + +## Mobile conformance and numeric types + +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, +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. + +## 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 + +### 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`, +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. + +`--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/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/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 } }, 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-boundaries-float.json b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json new file mode 100644 index 00000000..c7c4335c --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-float.json @@ -0,0 +1,1093 @@ +{ + "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": 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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + }, + "evaluation_count": 1 + } + ] + }, + "_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..4177018b --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-integer.json @@ -0,0 +1,1093 @@ +{ + "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": 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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + }, + "evaluation_count": 1 + } + ] + }, + "_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..4be91412 --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-boundaries-number.json @@ -0,0 +1,1093 @@ +{ + "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": 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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + }, + "evaluation_count": 1 + } + ] + }, + "_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 new file mode 100644 index 00000000..43e5f876 --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-float.json @@ -0,0 +1,503 @@ +{ + "name": "numeric-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-float-0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "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 + } + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-float-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-3", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "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": { + "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-float-7", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-8", + "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-float-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-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-float-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_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 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-6" + }, + "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": 1, + "flag": { + "key": "numeric-float-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "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": 1, + "flag": { + "key": "numeric-float-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-10" + }, + "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": 1, + "flag": { + "key": "numeric-float-11" + }, + "evaluation_count": 1 + } + ] + } +} diff --git a/precomputed-assignments/cases/evaluator/numeric-integer.json b/precomputed-assignments/cases/evaluator/numeric-integer.json new file mode 100644 index 00000000..2b4e63cf --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-integer.json @@ -0,0 +1,503 @@ +{ + "name": "numeric-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-integer-0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "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 + } + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-integer-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "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 + }, + "_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-integer-7", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-8", + "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-integer-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-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-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_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 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-6" + }, + "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": 1, + "flag": { + "key": "numeric-integer-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "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": 1, + "flag": { + "key": "numeric-integer-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-10" + }, + "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": 1, + "flag": { + "key": "numeric-integer-11" + }, + "evaluation_count": 1 + } + ] + } +} diff --git a/precomputed-assignments/cases/evaluator/numeric-number.json b/precomputed-assignments/cases/evaluator/numeric-number.json new file mode 100644 index 00000000..498ff4af --- /dev/null +++ b/precomputed-assignments/cases/evaluator/numeric-number.json @@ -0,0 +1,503 @@ +{ + "name": "numeric-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-number-0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "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 + } + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-number-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-3", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-6", + "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-7", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-8", + "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-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "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" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_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 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-6" + }, + "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": 1, + "flag": { + "key": "numeric-number-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "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": 1, + "flag": { + "key": "numeric-number-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-10" + }, + "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": 1, + "flag": { + "key": "numeric-number-11" + }, + "evaluation_count": 1 + } + ] + } +} diff --git a/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json b/precomputed-assignments/cases/evaluator/type-mismatch-all-types.json index a2f0dc68..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": "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 nonnumeric getters return TYPE_MISMATCH; an exact numeric conversion succeeds.", "context": { "targetingKey": "mismatch-user", "attributes": {} @@ -84,17 +84,11 @@ "variationType": "float", "defaultValue": 0.0, "expectedResult": { - "value": 0.0, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_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" - } - ] + "value": 7, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "var-int" + } }, { "flag": "fixture.mismatch.float-as-bool", @@ -121,14 +115,14 @@ ], "expectations": { "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], "evaluationEvents": [ { - "_count": 5, - "_skip": [ - { - "platform": "web" - } - ] + "_count": 5 } ] } 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/generated/evaluator/obfuscation-numeric-boundaries-float.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json new file mode 100644 index 00000000..90483ddb --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json @@ -0,0 +1,1094 @@ +{ + "name": "obfuscation-numeric-boundaries-float", + "description": "Generated obfuscated companion: 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": { + "35dd7544a6b9a875c5074a893aa3d5c95f015b6cff7f07f30390bc90b939839e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "54b3e4e77e354d4e049991350fe68ce1a90ba4e874933a9e269d5b99cb3ad115": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6cec42afe677ad01472e4754a611106037ddb8e786570bf9a730630b8d2f7f30": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "8de4314e7ce11a9f64a3490e59968a1130f56b15c39276bdff9ebc344402101d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4182dc078ece15bc1c45cabf6e2ab2ad0b7acaf7f42eaa3ad63fe3956beb1db7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ec7fd610e215fd5d177ffa68bec26d50a871bc1cf0114636a734e1660bd6411e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "53d34567f5337f1104d86a9e821df4511fa74a9cc03516f426c9ab4c7af0ac52": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3e69e701cc02087f7897b6f4832e8be0be9ca155876f8cb95597e6ad32b1e2ab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bd1a3e85e06626dbb1c56432a8dea7a0128284f1d781013835a2564e70f87d98": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a212f6d288564e940e009835514eaad55a3d62caada53dd1fdda1b2475efed0a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f8dc9d43242ec89a4db2212c3a70ee9c938b0cb36afac45b2000cd7c77bf7609": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b1e20092aa2b67b3e8ed9d5533a2260ff177207d9b86fac2151864b2d7af492e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "45f0d04656e98eb32b8221714cf169d57479d2be4af6b96c5ffe0f9fd589ad0a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "680bb5138700a197f7896709b20d6a451854dfaf7fd2016374154dc214101f96": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "5f0e609737393c76014f125cfe1d8b685065b3f8bb2833e393f4de4bb3f271fd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6de38d27b71396e8169bdf012bf53a9f6112b4f9533e492d1d0c372aa5b2b89a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e68a8c78bf6d545e6c949e88209611b6615ddeea9817440de9fd3fb7a289c335": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a6d6386fa18188d57ad46d118b937d77a1a8d814e3ff723871452b9d5f6abd03": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9d28b9ca14aed8cd8edab7144dc09ff50ff608f1676df0c92be1a225fc4c95b4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e026139b2c1ef067fcd194745a316b948ca17d4224a2c6ef0f54e42739a6f9e5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9ad722f4aa0e6cbe77da0e6406a3f40ac0dbe3f556a524d1d504115896235fed": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "56cd8c449d33e0a19ca9b0d300b40bd994dfd1f4f3b16d093e453d8a97a3645f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + }, + "evaluation_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-numeric-boundaries-integer.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json new file mode 100644 index 00000000..4acc8be2 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json @@ -0,0 +1,1094 @@ +{ + "name": "obfuscation-numeric-boundaries-integer", + "description": "Generated obfuscated companion: 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": { + "dba4f4d05c42d94f2f0fe7fb4c5622432cbace9983c4f2bcd81dba39a6327250": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c053d531267e94db3094b514ac132333832cf45cc102899e23a317cfd1b86c7c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "cc6b41d8abf103fc2c0903beb89ea4916203e65e34f56f229bd4bfd30fca2b44": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2cd758e8f6b3d6069426eaf4fe83cad350062d6f3cf9fdff78af6814151ff35f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "be1e08347c503a3116b8872a98f762ce80f8b2d5d9712855f553e9a8e41a0e24": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "439b8c652a5b3d6bb6a8b5601f86c09f38f1ca344dfa0cd10904c41d3a0089ba": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "52c0be60636eb11b987e1729b894a63f02463a408660d632004cd68b66c17966": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "32ac57434b06ca630219caa81b6d6c685138920a8a9d1e6205055cff023f3179": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "21bae751d95202679767297ac5a668ff2588c260e0c1d93e0ddb494a3c6c44d9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ceb204983395689386573ade9d3496ac4c07fde14fd7dbc965b51654af834df1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c2b69fdede340468e31ce79ee511016e1f3f15fceaf2228fb6bde3c034cb14d9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "0a1f9c3aedb3a5721f4215c26cd403a7bca6d5fcba088d6ff8c5eb58a4168903": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "cf564971ee029d99592fad8d056f2d1cb13ff91e3e283dc2c16b3a644db90734": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "482ab354d08783942a1cc1613d261773481b27647304272c9a895b3f82aef81b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "8ae623d95ec069f4263033a5c3c63cc41c8369a4ada5c2df1e72feb3472b3298": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3ee628175449c6db9aeeb2b77f155f2af7f566ae1209ed32f564b4e74d37ffbe": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1aaaeb79098a7feede64e211e53f192295e0d97db557223f5945bd869a9ec82c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1a9a9de8e21c848b2bfec22d14c770d8a3a176dd1ce39955a5b165bf1f851590": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c2dca5f665b034d94acdc589d0b6646e583561b3f05aeba0a0b6399297c8ccf1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a014f0066d631022d1c7628bd1b990291f772710900955e0aab48543db7c3b2a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "05ab2bff8b65224aff72672e380d66e022ff5f0c1b76e3231aaf172f6c82d467": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a10340fc40240dde6ba540065e6526b8c66b6fd78d116451b42201a9ba4c665d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + }, + "evaluation_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-numeric-boundaries-number.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json new file mode 100644 index 00000000..27bfa451 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json @@ -0,0 +1,1094 @@ +{ + "name": "obfuscation-numeric-boundaries-number", + "description": "Generated obfuscated companion: 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": { + "4151b6612247033495b8370b980c847c1efe5dc3ddb008a57aaf9e8afb97694d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c3c464e47d3e87d433d2a0b6a5ea9e9cea9ffb65c31a7022fc4be4c51a3e3663": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "d9adf2748e7c2bb330f6b321204a4ab3d61aa555dbeaddcb3d79fb20383e3840": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a3e931eb60a9b70284b948912691bbf4e1f757d1f52c90d1623413a4ed05eaf7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "56a5f99336ac7daaa327d074e3c1cf1389f3b0e198d8b6b0dc4a0773f37466b0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "996e91f621b0091b21aaceea06330f3bcc8a4639939a0b3cafd38cdd2d732bde": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "5b95fc3592f62934e978d8f18e998120d1012da96a4ae3c7a3bc832442914bce": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fb8ab09c443a1fef7f78aa00069193131bdddd5f754c69837da77dde8bd53ab4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "13bc44aee0e07075a5f61e06bea48f9c7d7cfe240bbcf45d9922127c5fcbf57a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2b8ee396fbeba43a943135d2ea6962f4eee41f3af704609313b6241bf4911f20": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9dec15ccf9649edff360ec3a223801aa6f804087a939b1d14b686a7fe34a0cbc": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b1aedbf12de210a49f962e5e8db09a065c2b328a7d2c48ce4c6e919325b9c0c5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4a989c369519e210c4857cd50fd525e571cc89b7da322276ba05487c15b4849f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "313de806bdcd55e96b13d12c28260bc5238057d4ed2a8ecc0294f8c7e234fa01": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1f5409b64eb99b424819230ddfb333b5f399a911840ab6b36f0a0938a438af25": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "61f7deca99e29b35ab18cdf82001dcf64ea3b04caaa9fc751d835f1c70d25fd2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "06503c386b2c84e0299e7a692baecdecf2b19698b90f13b89df7d4e309d43870": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6d9de4417c04ad9908dc846b1a9ae5c6c2fe214e7aca7e2dd3480d82abd46a5f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1b553f209415af2ef3dd59e7f7383e270e5090b9811e319c39645a51d58ec26c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4ccf6d4d18107b748ae8ec301e66adb190f15bd77e3bf23246d8994787c8a40c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9de54af153484d9db86d593c9a0c9443f7c5a88881964452b69b7f2d8a878c01": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "52b56cf56113aee71339a1256252ae52741aa5e666d2a5ca3ae3ad194cbb83f5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + }, + "evaluation_count": 1 + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_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." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + }, + "evaluation_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-numeric-float.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json new file mode 100644 index 00000000..bf65bb4b --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json @@ -0,0 +1,522 @@ +{ + "name": "obfuscation-numeric-float", + "description": "Generated obfuscated companion: 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": { + "6711bb1e8b093b846ea439322c667bce9ca0568b0f0d4320e05c236c9ead11ce": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "dd28ccc7dc267c0927246a422010adbf6e81bb5b0e8fa6a3f9c5c999dcfa8cac": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9662b5e2c026477337a6a1b952ca1da98116ea3393c14ad81bf3b0e6f7705eae": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6a126c45f099dc1465fb20627b24f64632aae36028757b78b361873c454d566e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7a331b89b73d723922e64da680ad3048d381db62fd6f9139eb9e57ea43569156": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e152b1e1e80ca9edabd8cfd196b0084ee4728b8eb6b85c1de743705b8ec3f1c5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "968e09a8af812ed8d7fc18b21f59d5b7623d5e1ae6fc8f5a141443f026b11aa7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b4b714689b3211e62d2fbde91099071f193e00c955f5a96a6ffa087b2e6bfa98": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a978ec7a2487b240242a05119affc348f41d0d9fbdce334665c4090c2f4ab1fd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3860419b6dc9dc69573f100ccbf51c008e45a8a9b6a62243ff02b869f4e86b47": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a303e6fa7efedf997c87edfc2d79872c95d3e1a6c5310ccfe65d0826f700ab55": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "41a0cf77c6270e0e6f3941ab2fa80f712b1c916d13793ddd36a99363c07ad885": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-float-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-3", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "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": { + "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-float-7", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-8", + "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-float-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-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-float-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_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 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-6" + }, + "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": 1, + "flag": { + "key": "numeric-float-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "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": 1, + "flag": { + "key": "numeric-float-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-10" + }, + "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": 1, + "flag": { + "key": "numeric-float-11" + }, + "evaluation_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-numeric-integer.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json new file mode 100644 index 00000000..e61afa1d --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json @@ -0,0 +1,522 @@ +{ + "name": "obfuscation-numeric-integer", + "description": "Generated obfuscated companion: 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": { + "6f6faa00700939aa0c395e059d8dea1cc48b2c5a20eb2ef5bc7abfeb551e2d6a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6418f8b5f4df9814bfd25f30a8ef6865929075706f260ca4f48f5334f5541c89": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "0f2ba6c096112e6b701607e345fc3a06a2a0d93710715cea905546b69b536ec0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ddd11d6e8fd3dfc9734f307630745484bb411a3278c36ad0dd7c681f91edcde5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b1108b8a690b2ca3dd55e74294ed8bfdf1496b88ed4b3cb87abf27199c6b819e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "01ede755b925df7f56ae33a0bcc4d822a0495b602eea740c5b8570c025527afa": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fc310ab0e0bd16d08c9b769db925a177fa89b3f539d75b01ef8dd319c427fccf": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ad9ca46d7714846bd2a813e74d2af97e1cd25df3b0242f1575db2083f6f9d8b1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "35eb2ca6e144c429db174c281e9e55d2b9b4e9d819a159203bbdf577354e4033": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "27bc83275f0f9a5e871d2e4c5af47829d9f7b72c3e5f1f224fbafe3cef986c81": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "19415991350c00c660f9d74c206db9ec7d9fbde7c8a8cbdcfd6775911e40be6b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a556037a239ae8b7c3789f59013e015ebf29686de47bce8aa1bb17f99569a4e8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-integer-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "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 + }, + "_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-integer-7", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-8", + "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-integer-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-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-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_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 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-6" + }, + "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": 1, + "flag": { + "key": "numeric-integer-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "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": 1, + "flag": { + "key": "numeric-integer-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-10" + }, + "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": 1, + "flag": { + "key": "numeric-integer-11" + }, + "evaluation_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-numeric-number.json b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json new file mode 100644 index 00000000..6ea6bdf9 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json @@ -0,0 +1,522 @@ +{ + "name": "obfuscation-numeric-number", + "description": "Generated obfuscated companion: 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": { + "5a1d7afff3fd522b50e8891f7af14267bdd253a1455ed12bc92188f78e12033d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "cbc15a556f7e611fa26f06f6a6334ee91ad5d6a344496452557db41ed992bc25": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f1c431305b153a3030fadebc0057e634b6162cd6acda9744a763082b1197c740": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7b3cbab857c42d32035705aa891dd7a2fbadcbe1fc0f1e28f3611076a6aa7945": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "acdfe2ec44b3eb617b86da9a49166e3abecbc85c00951e548516cfd748056403": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3af8ba8e93c58c248ed0d943b8bfbc630236bbbeca96be121efc46ed8da4b9b0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "efb49302883b084e49d51cb2f41fd795224a571d182536e203f6ab28e6648b77": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c38a503ec9a1c032f0a073e23d6d0ede2eccf245aac33f9db02a5374728c810e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "499aef4e9d9d7df120a79816e9406b5925664e851fd638a129f525588e8a9385": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b004cb3fb7dbb1846ba17f461b1a0931cd52d9ffde2c5419d51c0b47e42baf99": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "d858ab64de6a97983f95792030011b2dd635938aaefd90fee12b37ef67e9dac3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3ece2688265c5c214d2904619cf0294042830dc10d72dae32e50050c7cded8c3": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-number-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-3", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-6", + "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-7", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-number-8", + "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-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "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" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_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 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-6" + }, + "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": 1, + "flag": { + "key": "numeric-number-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "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": 1, + "flag": { + "key": "numeric-number-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-10" + }, + "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": 1, + "flag": { + "key": "numeric-number-11" + }, + "evaluation_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-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/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-reject-false-with-descriptor.json b/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json new file mode 100644 index 00000000..40a03668 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-false-with-descriptor.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..0c0d8633 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-descriptor.json @@ -0,0 +1,46 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..18741990 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-marker.json @@ -0,0 +1,49 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..ce837948 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-missing-salt.json @@ -0,0 +1,49 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..1cbb1a4e --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-digest.json @@ -0,0 +1,59 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..1c2c2ffb --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-nonhex-salt.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..e1d27358 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-descriptor.json @@ -0,0 +1,47 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..67a35537 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-marker.json @@ -0,0 +1,46 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..84350ae9 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-null-salt.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..94059a26 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-plaintext.json @@ -0,0 +1,59 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..0e2f913c --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-digest.json @@ -0,0 +1,59 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..2b54a393 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-short-salt.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..1c1a8bf9 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-string-marker.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..7e98d9e8 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-unknown-scheme.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..4d53d3e8 --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-digest.json @@ -0,0 +1,59 @@ +{ + "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." + }, + { + "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 new file mode 100644 index 00000000..2d89321f --- /dev/null +++ b/precomputed-assignments/cases/obfuscation/obfuscation-reject-uppercase-salt.json @@ -0,0 +1,50 @@ +{ + "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." + }, + { + "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-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/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 @@ +{} 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 + } + } + } + } + ] }