From 5b3ba68a805c944adc5bf76e2f53a1bb3ef4e2c7 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:28:00 -0500 Subject: [PATCH 1/8] FFL-3568: Validate shared precomputed responses and reject encodings --- .../self.precomputed-fixtures.sts.yaml | 9 + .github/workflows/precomputed-fixtures.yml | 86 ++++ ci/precomputed-fixtures-pr.md | 18 + ci/precomputed-fixtures.json | 3 + ci/sync-precomputed-fixtures.py | 178 +++++++ packages/core/README.md | 47 ++ packages/core/package.json | 1 + .../__tests__/CanonicalPrecomputed.test.ts | 134 +++++ .../__tests__/fixtures/precomputed/LICENSE | 201 ++++++++ .../__tests__/fixtures/precomputed/NOTICE | 4 + .../__tests__/fixtures/precomputed/README.md | 231 +++++++++ .../fixtures/precomputed/SOURCE.json | 48 ++ .../cases/emissions/all-doLog-false.json | 87 ++++ .../cases/emissions/duplicate-evaluation.json | 63 +++ .../cases/evaluator/all-types-success.json | 142 ++++++ .../defaults-and-emission-gates.json | 111 +++++ .../cases/evaluator/empty-flags-response.json | 64 +++ .../cases/evaluator/falsy-values.json | 129 +++++ .../integer-precision-native-only.json | 61 +++ .../cases/evaluator/multi-flag-set.json | 471 ++++++++++++++++++ .../cases/evaluator/multiple-reasons.json | 91 ++++ .../cases/evaluator/numeric-float.json | 291 +++++++++++ .../cases/evaluator/numeric-integer.json | 195 ++++++++ .../cases/evaluator/numeric-number.json | 261 ++++++++++ .../cases/evaluator/object-nested-deep.json | 87 ++++ .../evaluator/special-characters-in-keys.json | 108 ++++ .../evaluator/type-mismatch-all-types.json | 144 ++++++ .../cases/obfuscation/obfuscation-keys.json | 259 ++++++++++ .../obfuscation-numeric-float.json | 310 ++++++++++++ .../obfuscation-numeric-integer.json | 214 ++++++++ .../obfuscation-numeric-number.json | 280 +++++++++++ ...uscation-reject-false-with-descriptor.json | 50 ++ ...obfuscation-reject-missing-descriptor.json | 46 ++ .../obfuscation-reject-missing-marker.json | 49 ++ .../obfuscation-reject-missing-salt.json | 49 ++ .../obfuscation-reject-nonhex-digest.json | 59 +++ .../obfuscation-reject-nonhex-salt.json | 50 ++ .../obfuscation-reject-null-descriptor.json | 47 ++ .../obfuscation-reject-null-marker.json | 46 ++ .../obfuscation-reject-null-salt.json | 50 ++ .../obfuscation-reject-plaintext.json | 59 +++ .../obfuscation-reject-short-digest.json | 59 +++ .../obfuscation-reject-short-salt.json | 50 ++ .../obfuscation-reject-string-marker.json | 50 ++ .../obfuscation-reject-unknown-scheme.json | 50 ++ .../obfuscation-reject-uppercase-digest.json | 59 +++ .../obfuscation-reject-uppercase-salt.json | 50 ++ .../obfuscation/obfuscation-rotated-salt.json | 259 ++++++++++ .../obfuscation-unsupported-consumer.json | 47 ++ .../__tests__/fixtures/precomputed/index.json | 39 ++ .../fixtures/precomputed/schema.json | 400 +++++++++++++++ .../src/flags/configuration/precomputed.ts | 10 +- 52 files changed, 5905 insertions(+), 1 deletion(-) create mode 100644 .github/chainguard/self.precomputed-fixtures.sts.yaml create mode 100644 .github/workflows/precomputed-fixtures.yml create mode 100644 ci/precomputed-fixtures-pr.md create mode 100644 ci/precomputed-fixtures.json create mode 100644 ci/sync-precomputed-fixtures.py create mode 100644 packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/LICENSE create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/NOTICE create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/README.md create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/all-doLog-false.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/duplicate-evaluation.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/all-types-success.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/defaults-and-emission-gates.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/empty-flags-response.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/falsy-values.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/integer-precision-native-only.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multi-flag-set.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multiple-reasons.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/object-nested-deep.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/special-characters-in-keys.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-keys.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-false-with-descriptor.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-descriptor.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-marker.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-salt.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-nonhex-digest.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-nonhex-salt.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-descriptor.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-marker.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-salt.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-plaintext.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-short-digest.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-short-salt.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-string-marker.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-unknown-scheme.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-uppercase-digest.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-uppercase-salt.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-rotated-salt.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-unsupported-consumer.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/index.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/schema.json diff --git a/.github/chainguard/self.precomputed-fixtures.sts.yaml b/.github/chainguard/self.precomputed-fixtures.sts.yaml new file mode 100644 index 000000000..931d2446f --- /dev/null +++ b/.github/chainguard/self.precomputed-fixtures.sts.yaml @@ -0,0 +1,9 @@ +issuer: https://token.actions.githubusercontent.com +subject: repo:DataDog/dd-sdk-reactnative:ref:refs/heads/develop +claim_pattern: + event_name: "(schedule|workflow_dispatch)" + workflow_ref: 'DataDog/dd-sdk-reactnative/\.github/workflows/precomputed-fixtures\.yml@refs/heads/develop' + repository: "DataDog/dd-sdk-reactnative" +permissions: + contents: write + pull_requests: write diff --git a/.github/workflows/precomputed-fixtures.yml b/.github/workflows/precomputed-fixtures.yml new file mode 100644 index 000000000..8996657e7 --- /dev/null +++ b/.github/workflows/precomputed-fixtures.yml @@ -0,0 +1,86 @@ +name: Precomputed flag fixtures +on: + pull_request: + paths: + - 'ci/*precomputed*' + - 'packages/core/src/flags/__tests__/fixtures/precomputed/**' + - '.github/workflows/precomputed-fixtures.yml' + schedule: + - cron: '23 8 * * 1' + workflow_dispatch: +permissions: + contents: read +concurrency: + group: precomputed-fixtures-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: false +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Verify the local snapshot + run: python3 ci/sync-precomputed-fixtures.py --verify + - name: Compare with the recorded upstream commit + run: python3 ci/sync-precomputed-fixtures.py --check + update: + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + id-token: write + steps: + - uses: DataDog/dd-octo-sts-action@c85802a58e202b7e764b5441e4378c76043c8108 # v1.0.5 + id: token + with: + scope: DataDog/dd-sdk-reactnative + policy: self.precomputed-fixtures + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Import upstream fixtures + id: sync + run: python3 ci/sync-precomputed-fixtures.py --ref main + - name: Prepare a draft update + id: prepare + if: steps.sync.outputs.changed == 'true' + env: + GH_TOKEN: ${{ steps.token.outputs.token }} + SOURCE_COMMIT: ${{ steps.sync.outputs.source_commit }} + run: | + existing=$(gh pr list --state open --search '"Update precomputed flag fixtures" in:title' --json number --jq 'length') + if [ "$existing" != 0 ]; then + echo 'An update PR is already open. Merge or close it before the next update.' + exit 0 + fi + branch="automation/precomputed-fixtures-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + git add -- 'packages/core/src/flags/__tests__/fixtures/precomputed' + echo "branch=$branch" >> "$GITHUB_OUTPUT" + echo 'ready=true' >> "$GITHUB_OUTPUT" + python3 - <<'BODY' + import os + from pathlib import Path + template = Path('ci/precomputed-fixtures-pr.md').read_text() + Path('/tmp/precomputed-pr.md').write_text(template.replace('SOURCE_COMMIT', os.environ['SOURCE_COMMIT'])) + BODY + - name: Create a signed fixture commit + if: steps.prepare.outputs.ready == 'true' + uses: DataDog/commit-headless@d6afabfd690ea1dbdd7d368acbefd71304928699 # action/v3.4.1 + with: + command: commit + token: ${{ steps.token.outputs.token }} + branch: ${{ steps.prepare.outputs.branch }} + head-sha: ${{ github.sha }} + create-branch: true + force: false + message: 'FFL-3568: Update precomputed flag fixtures' + - name: Open the draft pull request + if: steps.prepare.outputs.ready == 'true' + env: + GH_TOKEN: ${{ steps.token.outputs.token }} + UPDATE_BRANCH: ${{ steps.prepare.outputs.branch }} + run: | + gh pr create --draft --base develop --head "$UPDATE_BRANCH" --title 'FFL-3568: Update precomputed flag fixtures' --body-file /tmp/precomputed-pr.md diff --git a/ci/precomputed-fixtures-pr.md b/ci/precomputed-fixtures-pr.md new file mode 100644 index 000000000..1dd082644 --- /dev/null +++ b/ci/precomputed-fixtures-pr.md @@ -0,0 +1,18 @@ +### What does this PR do? + +Refresh the shared precomputed response fixtures to `SOURCE_COMMIT`. + +### Motivation + +Keep mobile response tests aligned with the canonical JSON contract. Ref: FFL-3568. + +### Additional Notes + +The existing fixture loop runs new cases in normal SDK CI. Review those results before merge. + +### Review checklist (to be filled by reviewers) + +- [ ] Feature or bugfix MUST have appropriate tests +- [ ] Make sure you discussed the feature or bugfix with the maintaining team in an Issue +- [ ] Make sure each commit and the PR mention the Issue number (cf the [CONTRIBUTING](CONTRIBUTING.md) doc) +- [ ] If this PR is auto-generated, please make sure also to manually update the code related to the change diff --git a/ci/precomputed-fixtures.json b/ci/precomputed-fixtures.json new file mode 100644 index 000000000..1d0e900b2 --- /dev/null +++ b/ci/precomputed-fixtures.json @@ -0,0 +1,3 @@ +{ + "destination": "packages/core/src/flags/__tests__/fixtures/precomputed" +} diff --git a/ci/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py new file mode 100644 index 000000000..d47e01c1f --- /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 +from pathlib import Path +import re +import subprocess +import tempfile +import uuid + +REPOSITORY = 'https://github.com/DataDog/ffe-system-test-data.git' +PREFIX = 'precomputed-assignments/' +FILES = { + 'schemas/precomputed-assignment.schema.json': 'schema.json', + 'precomputed-assignments/README.md': 'README.md', + 'LICENSE': 'LICENSE', + 'NOTICE': 'NOTICE', +} + + +def git(source, *args): + return subprocess.check_output(['git', '-C', str(source), *args]) + + +def snapshot(source, ref): + if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._/-]*', ref) or '..' in ref: + raise ValueError('Invalid fixture revision') + sha = git(source, 'rev-parse', '--verify', ref + '^{commit}').decode().strip() + if not re.fullmatch(r'[0-9a-f]{40}', sha): + raise ValueError('Expected a full source commit SHA') + result = {} + for entry in git(source, 'ls-tree', '-rz', '--full-tree', sha).split(b'\0'): + if not entry: + continue + metadata, raw_path = entry.split(b'\t', 1) + path = raw_path.decode() + target = FILES.get(path) + if path.startswith(PREFIX + 'cases/') and path.endswith('.json'): + target = path[len(PREFIX):] + if target is None: + continue + mode, kind, blob = metadata.decode().split() + if mode != '100644' or kind != 'blob': + raise ValueError('Fixture entries must be ordinary files: ' + path) + result[target] = git(source, 'cat-file', 'blob', blob) + cases = sorted(p for p in result if p.startswith('cases/')) + if not cases or not all(p in result for p in FILES.values()): + raise ValueError('Missing or empty precomputed fixture corpus') + for path in cases: + data = json.loads(result[path]) + if not isinstance(data, dict) or data.get('name') != Path(path).stem: + raise ValueError('Invalid fixture identity: ' + path) + if not isinstance(data.get('evaluations'), list): + raise ValueError('Missing evaluations: ' + path) + result['index.json'] = (json.dumps(cases, indent=2) + '\n').encode() + result['SOURCE.json'] = (json.dumps({ + 'repository': REPOSITORY, + 'commit': sha, + 'files': {p: hashlib.sha256(v).hexdigest() for p, v in sorted(result.items())}, + }, indent=2) + '\n').encode() + return result + + +def read_snapshot(destination): + if not destination.exists(): + return {} + result = {} + for path in destination.rglob('*'): + if path.is_symlink(): + raise ValueError('Snapshot must not contain symbolic links') + if path.is_file() and path.suffix != '.meta': + result[path.relative_to(destination).as_posix()] = path.read_bytes() + return result + + +def 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/packages/core/README.md b/packages/core/README.md index 6b61e7510..61536acd1 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -128,3 +128,50 @@ of your [application sandbox][4], which can't be read by any other app installed [2]: https://raw.githubusercontent.com/DataDog/dd-sdk-reactnative/main/docs/image_reactnative.png [3]: https://source.android.com/security/app-sandbox [4]: https://support.apple.com/guide/security/security-of-runtime-process-sec15bfe098e/web + +## Shared precomputed response tests + +The Flags module uses the JSON contract from [ffe-system-test-data](https://github.com/DataDog/ffe-system-test-data/tree/main/precomputed-assignments). +This keeps response behavior consistent across mobile SDKs. The snapshot is test data only. +It adds no runtime dependency and requires no Git submodule. + +The generated snapshot is at `packages/core/src/flags/__tests__/fixtures/precomputed`. `SOURCE.json` records the source commit and file digests. +The runner discovers all nested JSON cases. +Adding a case requires an upstream fixture change and snapshot refresh. It requires no runner or filename-list change. +Missing or empty fixtures fail the suite. Shared `_include` and `_skip` fields describe API applicability. +Do not add local exclusions to hide failures. + +Native Integer getters accept `integer`. Native Double getters accept `float`. +Legacy `number` accepts Double reads and representable integral Integer reads. +A whole-valued `float` still fails an Integer read with `TYPE_MISMATCH`. +React Native's Number API has no separate integer type. +These rules do not enable a new server wire format. Sending specific numeric types requires compatible capability negotiation. + +Cases cover values, defaults, errors, reasons, variants, emission gates, duplicate evaluations, and numeric boundaries. +Obfuscation cases cover fixed digests, salts, Unicode, whitespace, malformed metadata, and plaintext event identity. +React Native has one Number getter. Cross-type Integer/Double cases are excluded by the shared contract. The suite checks the real decoder, public getters, and native tracking arguments. It does not validate native event serialization or delivery. Encoded responses are rejected because this decoder does not support key obfuscation. + +Run from the repository root: + +```sh +python3 ci/sync-precomputed-fixtures.py --verify +yarn jest --runInBand --runTestsByPath packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts +``` + +Refresh the snapshot from a reviewed upstream commit: + +```sh +python3 ci/sync-precomputed-fixtures.py --ref +python3 ci/sync-precomputed-fixtures.py --check +``` + +The importer copies committed bytes, regenerates the index, and removes deleted cases. +It preserves Unity asset GUIDs where applicable. Do not edit imported JSON or its checksums. +Use `--source --ref ` to test an upstream fixture branch before publication. +`--verify` checks local integrity without networking. `--check` also compares the recorded upstream commit. + +The weekly `Precomputed flag fixtures` workflow opens a draft update PR when fixture content changes. +It uses a short-lived token restricted to the default branch and this workflow. +It waits when an update PR is already open. Normal SDK CI must pass before merge. +Merge the upstream fixture contract before merging its consumer PRs. +The update workflow never merges or publishes an SDK release. diff --git a/packages/core/package.json b/packages/core/package.json index 3c9948b6c..26669404c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,6 +18,7 @@ "main": "lib/commonjs/index", "files": [ "src/**", + "!src/flags/__tests__/fixtures/**", "lib/**", "scripts/**", "android/build.gradle", diff --git a/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts b/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts new file mode 100644 index 000000000..639641d26 --- /dev/null +++ b/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts @@ -0,0 +1,134 @@ +/* + * 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 2016-Present Datadog, Inc. + */ + +import fs from 'fs'; +import path from 'path'; +import { NativeModules } from 'react-native'; + +import { DdFlags } from '../DdFlags'; +import { + decodePrecomputedFlags, + UnsupportedConfigurationError +} from '../configuration/precomputed'; + +jest.mock('../../InternalLog', () => ({ + InternalLog: { log: jest.fn() }, + DATADOG_MESSAGE_PREFIX: 'DATADOG:' +})); + +type Filters = { + _skip?: { platform: string }[]; + _include?: { platform: string }[]; +}; +const applies = (value: Filters): boolean => + !value._skip?.some(item => item.platform === 'react-native') && + (!value._include || + value._include.some(item => item.platform === 'react-native')); +const root = path.join(__dirname, 'fixtures/precomputed'); +const discover = (directory: string): string[] => + fs + .readdirSync(directory, { withFileTypes: true }) + .flatMap(entry => { + const filename = path.join(directory, entry.name); + return entry.isDirectory() + ? discover(filename) + : entry.name.endsWith('.json') + ? [filename] + : []; + }) + .sort(); +const files = discover(path.join(root, 'cases')); +if (!files.length) { + throw new Error('Empty canonical precompute corpus'); +} +const fixtures = files.map( + filename => + [ + path.relative(root, filename), + JSON.parse(fs.readFileSync(filename, 'utf8')) + ] as const +); +if (!fixtures.some(([, fixture]) => applies(fixture))) { + throw new Error('All canonical fixtures excluded'); +} +const source = JSON.parse( + fs.readFileSync(path.join(root, 'SOURCE.json'), 'utf8') +); + +describe(`canonical precompute ${source.commit}`, () => { + beforeEach(async () => { + jest.clearAllMocks(); + Object.assign(DdFlags, { isFeatureEnabled: false, clients: {} }); + await DdFlags.enable(); + }); + + fixtures.forEach(([filename, fixture]) => { + (applies(fixture) ? it : it.skip)(filename, async () => { + if (fixture.expectedResponseError) { + expect(fixture.expectedResponseError).toBe('INVALID_RESPONSE'); + expect(() => decodePrecomputedFlags(fixture.response)).toThrow( + UnsupportedConfigurationError + ); + expect( + NativeModules.DdFlags.trackEvaluation + ).not.toHaveBeenCalled(); + return; + } + const decoded = decodePrecomputedFlags(fixture.response); + jest.spyOn( + NativeModules.DdFlags, + 'setEvaluationContext' + ).mockResolvedValue(Object.fromEntries(decoded)); + const client = DdFlags.getClient('canonical'); + await client.setEvaluationContext(fixture.context); + let tracked = 0; + for (const evaluation of fixture.evaluations.filter(applies)) { + const { flag, defaultValue, expectedResult } = evaluation; + const details = (() => { + switch (evaluation.variationType) { + case 'boolean': + return client.getBooleanDetails(flag, defaultValue); + case 'string': + return client.getStringDetails(flag, defaultValue); + case 'integer': + case 'float': + return client.getNumberDetails(flag, defaultValue); + case 'object': + return client.getObjectDetails(flag, defaultValue); + default: + throw new Error( + `Unsupported canonical getter: ${evaluation.variationType}` + ); + } + })(); + const actual = { + value: details.value, + reason: details.reason, + errorCode: details.errorCode ?? null, + variantKey: details.variant ?? null + }; + expect(actual.value).toEqual(expectedResult.value); + expect(actual).toMatchObject(expectedResult); + if (!details.errorCode) { + tracked += 1; + expect( + NativeModules.DdFlags.trackEvaluation + ).toHaveBeenNthCalledWith( + tracked, + 'canonical', + flag, + decoded.get(flag), + fixture.context.targetingKey, + fixture.context.attributes + ); + } + expect( + NativeModules.DdFlags.trackEvaluation + ).toHaveBeenCalledTimes(tracked); + } + }); + }); +}); diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/LICENSE b/packages/core/src/flags/__tests__/fixtures/precomputed/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/NOTICE b/packages/core/src/flags/__tests__/fixtures/precomputed/NOTICE new file mode 100644 index 000000000..cec0e71a2 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/NOTICE @@ -0,0 +1,4 @@ +Datadog ffe-system-test-data +Copyright 2026-Present Datadog, Inc. + +This product includes software developed at Datadog (https://www.datadoghq.com/). diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/README.md b/packages/core/src/flags/__tests__/fixtures/precomputed/README.md new file mode 100644 index 000000000..0a0791168 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/README.md @@ -0,0 +1,231 @@ +# Precomputed Assignment Fixtures (`precomputed-assignments/`) + +These fixtures cover the Datadog precompute assignment API consumed by mobile +and browser SDKs which do not run the evaluator locally. + +## Directory structure + +``` +precomputed-assignments/ + cases/ + evaluator/ # Core value resolution, types, defaults, errors + emissions/ # Exposure and evaluation event behavior + parsing/ # Malformed data handling, isolation (future) + README.md +``` + +Test runners should recursively discover `*.json` files under `cases/`. + +## Fixture structure + +Each fixture file is a single JSON object: + +```json +{ + "name": "fixture-name", + "description": "What this fixture tests.", + "context": { + "targetingKey": "user-id", + "attributes": {} + }, + "response": { ... }, + "evaluations": [ ... ], + "expectations": { ... } +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | yes | Fixture identifier; must match filename stem | +| `description` | string | yes | What the fixture tests | +| `context` | object | yes | `targetingKey` and `attributes` for the evaluation context | +| `response` | object | yes | Mocked precomputed-assignments response (open-ended) | +| `evaluations` | array | yes | Typed flag evaluations to run | +| `expectations` | object | yes | Event matchers (see below) | +| `_skip` | array | no | Skip entire fixture for listed platforms | +| `_include` | array | no | Run fixture only for listed platforms | + +## Evaluations + +Each evaluation calls a typed getter on the SDK and asserts the result: + +```json +{ + "flag": "flag-key", + "variationType": "boolean", + "defaultValue": false, + "expectedResult": { + "value": true, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "on" + } +} +``` + +`defaultValue` and `expectedResult.value` must use the JSON type declared by +`variationType`: boolean, string, integer, number for `float`, or object. +`float` accepts any JSON number; `integer` accepts only integral JSON numbers +(including `1.0`). Arrays and null are not valid for either field. + +Evaluations may carry `_skip` or `_include` to control per-platform applicability. + +## Expectations and event matchers + +The `expectations` block uses ordered, exclusive matchers to assert on emitted +exposure and evaluation events. + +```json +"expectations": { + "noUnmatchedEvents": true, + "exposures": [ + {"_count": 1, "flag": "my-flag"} + ], + "evaluationEvents": [ + {"_count": 2, "_skip": [{"platform": "web"}]}, + {"_count": 1, "_include": [{"platform": "web"}], "evaluation_count": 2} + ] +} +``` + +### Matcher semantics + +Matchers are processed **in order**. Each received event is consumed by the +first matcher whose non-`_` fields are a subset of the event. Once consumed, +the event is unavailable to later matchers. + +- `_count` *(required)*: how many events this matcher should consume. +- All other non-`_` keys are match fields compared against event data. +- A match field set to `null` asserts the field is either absent or null on + the actual event. +- A matcher with only `_count` (no match fields) matches any event. + +### `noUnmatchedEvents` + +When `true`, the test runner asserts that all received events (both exposures +and evaluations) are consumed by matchers. No extra events allowed. Applies to +both event types. + +### Control fields + +All fields prefixed with `_` are control fields, not match fields: + +| Field | Description | +|-------|-------------| +| `_count` | Number of events to consume (required on matchers) | +| `_skip` | Skip this matcher/evaluation/fixture for listed platforms | +| `_include` | Apply this matcher/evaluation/fixture only for listed platforms | + +Test runners in dynamic languages should filter keys starting with `_` to +separate control from match fields. Typed runners can use the known set. + +## `_skip` and `_include` + +Platform filters are always an array of `{platform, reason?}` objects, +regardless of where they appear (case, evaluation, or matcher level). + +```json +"_skip": [ + {"platform": "web", "reason": "Browser SDK aggregates differently."} +] + +"_include": [ + {"platform": "web"} +] +``` + +- **Default** (neither present): applies to all platforms. +- `_skip`: applies to all platforms except listed. +- `_include`: applies only to listed platforms. +- **Mutually exclusive**: specifying both is invalid. +- `reason` is optional. + +Valid platform identifiers: `web`, `ios`, `android`, `react-native`, `flutter`, +`maui`, `kotlin-multiplatform`, `electron`, `unity`. + +## How to implement a test runner + +1. Recursively discover `*.json` files under `precomputed-assignments/cases/`. +2. For each fixture, check `_skip`/`_include` at case level. Skip if the + current platform is excluded. +3. Initialize the SDK with `context`. Feed `response` as the mocked server + payload. +4. Walk `evaluations` in order. For each evaluation not skipped for the current + platform, call the typed getter (e.g., `getBooleanValue`) with `flag`, + `defaultValue`. Assert against `expectedResult`. +5. Flush the SDK. +6. Collect emitted exposure and evaluation events. +7. Walk `expectations.exposures` matchers in order. For each applicable matcher + (not skipped for the current platform), greedily consume up to `_count` + events whose fields are a superset of the matcher's non-`_` fields. Fail if + fewer than `_count` matched. +8. Repeat step 7 for `expectations.evaluationEvents`. +9. If `noUnmatchedEvents` is `true`, fail if any unconsumed events remain. + +## Field naming + +- All schema field names use **camelCase** (`expectedResult`, `variationType`). +- Snake_case in matchers is only for wire-format fields that match emitted + event schemas (e.g., `evaluation_count`, `serial_id`). +- Fixture filenames use **kebab-case**. + +## Schema and validation + +The fixture schema is defined in `schemas/precomputed-assignment.schema.json`. +Validate fixtures locally with: + +```bash +python3 ci/validate-precomputed-fixtures.py +``` + +## Mobile conformance and numeric types + +Native Integer getters accept `integer`. Native Double getters accept `float`. +The declared type controls this decision even when a `float` value is whole. +Legacy `number` accepts Double reads and representable integral Integer reads. +Fractional Integer reads return the default with `TYPE_MISMATCH`. +Single-Number APIs use the explicit evaluation filters for cross-type tests. + +Discover cases recursively. Do not maintain a filename list. Fail when the +corpus is missing or empty. Report the fixture revision, executed evaluations, +and exclusions. New JSON files require a snapshot refresh, not runner edits. +The generated `index.json` supports environments that cannot enumerate packaged +resources. It must be regenerated from the complete directory, never edited. + +`expectedResponseError: "INVALID_RESPONSE"` requires rejection of the complete +response before installation. Such a case has no evaluations. Assert rejection +explicitly; do not catch every exception as a successful expected rejection. + +The obfuscation cases use `flag-key-sha256-v1`. Hash the UTF-8 domain prefix +`datadog.feature-flags.flag-key.v1\0`, the decoded 16-byte salt, and the unchanged +UTF-8 flag key. Digest vectors are fixed JSON data. Never derive an expected +digest using the SDK being tested. Lookup uses the caller's plaintext key; +emitted events retain that plaintext identity. Missing encoded lookups must not +retry plaintext lookup. Unsupported consumers must reject encoded responses. + +The corpus checks duplicate evaluations after one flush. Aggregating consumers +emit one event with `evaluation_count: 2`. Exposure deduplication is independent. +Runners must inspect real event records, not synthesize them from expectations. +Keep native bridge and deployed-backend proof separate from mocked conformance. + +## Snapshot maintenance + +Consumers can copy `ci/sync-precomputed-fixtures.py` and configure the destination +in `ci/precomputed-fixtures.json`. The importer copies committed source bytes, +records the full SHA and file digests in `SOURCE.json`, generates `index.json`, +and removes deleted cases. `--check` compares the complete snapshot with its +recorded source commit. `--source` permits testing a committed local fixture +branch before publication. Tests use the checked-in snapshot without networking. + +Fixture PRs can expose existing SDK defects. Record those failures rather than +changing expectations or adding local skips. Platform filters describe API +applicability, not temporary implementation failures. `unity` is the identifier +for the Unity Flags implementation. + +`--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/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json new file mode 100644 index 000000000..c4da2bf85 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json @@ -0,0 +1,48 @@ +{ + "repository": "https://github.com/DataDog/ffe-system-test-data.git", + "commit": "e4f93740f783ddc0823be5f15daaf78c01869d48", + "files": { + "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", + "NOTICE": "a5245f10dbda39cc65bad11ab3a6022b9e5c05b259da3f7aa0302d932f31083e", + "README.md": "0c29b94146aa81088dc0e70435019f6d8bded371a068d566768ecdb2e7cb86e1", + "cases/emissions/all-doLog-false.json": "1902637564cde9a7105a80730bfbeb9ce57178a59910c905fad03d7a1118b4de", + "cases/emissions/duplicate-evaluation.json": "88c1dbdcf3c1bb1ee8dd9a0842123447921b9d9fe806f26d24cc41a7d73bb0cc", + "cases/evaluator/all-types-success.json": "ff22e47d5bda8a1fef19892c853e4396ccdb8509a3741888eb69169847fc4fde", + "cases/evaluator/defaults-and-emission-gates.json": "fd9c306086b7ce87cacd7f69d2f2c7af03921fffa9b43455dcc89dc17f3c3bfd", + "cases/evaluator/empty-flags-response.json": "f9ddb39e6a425b2c3f7884c7bdf92516e5ebac5b0ed4d5b7be6aa5e38950ea99", + "cases/evaluator/falsy-values.json": "93f26466d4536563c38619499275e2efe9f6c98ce2bc10c55ad57b15292b6647", + "cases/evaluator/integer-precision-native-only.json": "96c4bed183bfeeab7b59209dcdf5b36726e417f1035a28198ef95642ea673622", + "cases/evaluator/multi-flag-set.json": "b1c76977764c1bc11f430a50d33a06bb33daa60ed0d184547250362559f34a0b", + "cases/evaluator/multiple-reasons.json": "812b48f5131814fa7e13b3779b47f8805e59a7b063b5737b17139d01111c0134", + "cases/evaluator/numeric-float.json": "f8ce19ac8b0cf9e5750a9eb54975e056bfa53e358015e6b36e84025e3799eecd", + "cases/evaluator/numeric-integer.json": "9522fe4f68e3b8a48aac3cb2ed60fd254ced98f9b8d29eb61eeaa32ce521388c", + "cases/evaluator/numeric-number.json": "dc3b2b0086f9fb0d24e5e5f41249bbb78a77f5880adf22f05e288e9191a8077a", + "cases/evaluator/object-nested-deep.json": "22f8f0c516cfe0456d34697f29bbf39d7736559e0b188de4671d0fee6545fd3d", + "cases/evaluator/special-characters-in-keys.json": "07e24ba1a407ef9604af0bc5ab3f8faafe199dd83db94d96d0e3d689430054ce", + "cases/evaluator/type-mismatch-all-types.json": "1c4eb5a6407cca9ca9c8e7d5fd09d241cb8296cb5b42c9a56432cc278a86933a", + "cases/obfuscation/obfuscation-keys.json": "3af487d59d5a7064e0e8e6508343db21689180d7d6e862b055934036e1716643", + "cases/obfuscation/obfuscation-numeric-float.json": "92760c90a0d289fc9c8ae38323bc81b5c6a5608d43a05b7b9c4831c097b971bd", + "cases/obfuscation/obfuscation-numeric-integer.json": "eab193a1eb876522c37eb041e704efa91a00c0c92304d52a65897882e589dc35", + "cases/obfuscation/obfuscation-numeric-number.json": "d05e213350654a8f4fd08883316dd10ed7e2a5be51383b8a3c94fd025027833b", + "cases/obfuscation/obfuscation-reject-false-with-descriptor.json": "452e57fbcec5a254c65295014ae11675d406a87567e6887933ba493a14109093", + "cases/obfuscation/obfuscation-reject-missing-descriptor.json": "f0105da777c3b35ff10c7082e62ddd1f7ce6a4bc15dae27302f279b18b3bfc29", + "cases/obfuscation/obfuscation-reject-missing-marker.json": "4fb270faacdcfe92477120f9bf9ea56f98e1503cf992c72da71ecc6fbca5e0dc", + "cases/obfuscation/obfuscation-reject-missing-salt.json": "3f570926fd565dbcbdffa0ba83c7142fbffa85e1092d9562f190194b6e746a59", + "cases/obfuscation/obfuscation-reject-nonhex-digest.json": "64f7a96b1ee94cc978fd6ba5dcf4fe10652402ca0cab49c7091c5529b26e9bde", + "cases/obfuscation/obfuscation-reject-nonhex-salt.json": "4813bf66119b890770029039d70ce8dbb8e7749bae808ab3cf953b6ddbf40625", + "cases/obfuscation/obfuscation-reject-null-descriptor.json": "85010c74f15be5a2188c3f534539e82e4138ba1b182bf94b8036348753e3dfa8", + "cases/obfuscation/obfuscation-reject-null-marker.json": "c11bcc39b8235f278d95c9848c47d43ecda5649fe2c6156f37266ffe710eb375", + "cases/obfuscation/obfuscation-reject-null-salt.json": "c7c93a8fe80b4ba3704112124d95b58caff95ac3c07cff232031b4a87626bc61", + "cases/obfuscation/obfuscation-reject-plaintext.json": "5487e40a247337ed0c4ed7921bd62b7232b28309839de91ecfa5ba3d39fd4bf4", + "cases/obfuscation/obfuscation-reject-short-digest.json": "90e7aa899491117416d10989589f574bb5e530394fbaa4b460924083941ee29f", + "cases/obfuscation/obfuscation-reject-short-salt.json": "3e71c820b5eb0e19da5dbd0af088b43b9a854a7f367066576d096ef33770a1be", + "cases/obfuscation/obfuscation-reject-string-marker.json": "a22240089a26b1528ca07f1f7c958d8f7535438e2b47ad97fb717944eef73ad9", + "cases/obfuscation/obfuscation-reject-unknown-scheme.json": "ba4297333f5b8eed888e74a64dacb83d8db7ac111bc5465d61ec07b523331fc5", + "cases/obfuscation/obfuscation-reject-uppercase-digest.json": "a7e8b2de615fc55e5d39cca5ea42d1dfe14dc7a15b40db0d012a80992cb5943e", + "cases/obfuscation/obfuscation-reject-uppercase-salt.json": "65f4a35f6042863cbec5f19634fa9526da1e6c66ddecf3aad488f4455e26ec7e", + "cases/obfuscation/obfuscation-rotated-salt.json": "a97bf1f0d57ad66d122a4286433ba216469d8de2824e03b2d79fd860b87e277d", + "cases/obfuscation/obfuscation-unsupported-consumer.json": "d632485351b4b6cb7b550769cde55c46960ebee7ac811059a5147f143524554f", + "index.json": "07d8036a97955a47a5ea5e840c7f612f69b2f13bce67ddbd636d2a6dfdde6a43", + "schema.json": "0353d393b1b4193346b2fa907f500b5c60be51318759c3afe52e549f1662195d" + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/all-doLog-false.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/all-doLog-false.json new file mode 100644 index 000000000..701d304e2 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/all-doLog-false.json @@ -0,0 +1,87 @@ +{ + "name": "all-doLog-false", + "description": "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": { + "fixture.silent.bool": { + "allocationKey": "alloc-silent", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "fixture.silent.string": { + "allocationKey": "alloc-silent", + "variationKey": "greeting", + "variationType": "string", + "variationValue": "hello", + "reason": "TARGETING_MATCH", + "doLog": false + }, + "fixture.silent.int": { + "allocationKey": "alloc-silent", + "variationKey": "limit-ten", + "variationType": "integer", + "variationValue": 10, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/duplicate-evaluation.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/duplicate-evaluation.json new file mode 100644 index 000000000..0e26bc826 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/duplicate-evaluation.json @@ -0,0 +1,63 @@ +{ + "name": "duplicate-evaluation", + "description": "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": { + "fixture.dedup.flag": { + "allocationKey": "alloc-dedup", + "variationKey": "variant-a", + "variationType": "string", + "variationValue": "alpha", + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/all-types-success.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/all-types-success.json new file mode 100644 index 000000000..b2d1b3d60 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/all-types-success.json @@ -0,0 +1,142 @@ +{ + "name": "all-types-success", + "description": "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": { + "flutter.fixture.enabled": { + "allocationKey": "allocation-success", + "variationKey": "enabled", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flutter.fixture.title": { + "allocationKey": "allocation-success", + "variationKey": "copy-a", + "variationType": "string", + "variationValue": "Datadog Flags", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flutter.fixture.limit": { + "allocationKey": "allocation-success", + "variationKey": "limit-five", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flutter.fixture.ratio": { + "allocationKey": "allocation-success", + "variationKey": "half", + "variationType": "float", + "variationValue": 0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flutter.fixture.config": { + "allocationKey": "allocation-success", + "variationKey": "object-a", + "variationType": "object", + "variationValue": { + "showBanner": true, + "colors": [ + "blue", + "green" + ] + }, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/defaults-and-emission-gates.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/defaults-and-emission-gates.json new file mode 100644 index 000000000..bf906705d --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/defaults-and-emission-gates.json @@ -0,0 +1,111 @@ +{ + "name": "defaults-and-emission-gates", + "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": { + "plan": "free", + "platform": "flutter" + } + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "flutter.fixture.silent": { + "allocationKey": "allocation-silent", + "variationKey": "silent-on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "flutter.fixture.string": { + "allocationKey": "allocation-string", + "variationKey": "copy-b", + "variationType": "string", + "variationValue": "Actual string", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flutter.fixture.unknown": { + "allocationKey": "allocation-unknown", + "variationKey": "unknown-a", + "variationType": "unsupported", + "variationValue": "Ignored", + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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" + } + ] + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/empty-flags-response.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/empty-flags-response.json new file mode 100644 index 000000000..0114c621c --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/empty-flags-response.json @@ -0,0 +1,64 @@ +{ + "name": "empty-flags-response", + "description": "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": {} + } + } + }, + "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" + } + ] + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/falsy-values.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/falsy-values.json new file mode 100644 index 000000000..ecb04d764 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/falsy-values.json @@ -0,0 +1,129 @@ +{ + "name": "falsy-values", + "description": "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": { + "fixture.falsy.bool": { + "allocationKey": "alloc-falsy", + "variationKey": "off", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.falsy.int": { + "allocationKey": "alloc-falsy", + "variationKey": "zero", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.falsy.float": { + "allocationKey": "alloc-falsy", + "variationKey": "zero-f", + "variationType": "float", + "variationValue": 0.0, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.falsy.string": { + "allocationKey": "alloc-falsy", + "variationKey": "empty", + "variationType": "string", + "variationValue": "", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.falsy.object": { + "allocationKey": "alloc-falsy", + "variationKey": "empty-obj", + "variationType": "object", + "variationValue": {}, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/integer-precision-native-only.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/integer-precision-native-only.json new file mode 100644 index 000000000..a938cc694 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/integer-precision-native-only.json @@ -0,0 +1,61 @@ +{ + "name": "integer-precision-native-only", + "description": "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": { + "fixture.large-int": { + "allocationKey": "allocation-a", + "variationKey": "large", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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": "The native integer API preserves signed 64-bit integers. Browser Dart must report separate applicability." + }, + { + "platform": "flutter", + "reason": "The native integer API preserves signed 64-bit integers. Browser Dart must report separate applicability." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multi-flag-set.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multi-flag-set.json new file mode 100644 index 000000000..303b816d5 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multi-flag-set.json @@ -0,0 +1,471 @@ +{ + "name": "multi-flag-set", + "description": "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": { + "fixture.large.flag-01": { + "allocationKey": "alloc-l", + "variationKey": "v1", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-02": { + "allocationKey": "alloc-l", + "variationKey": "v2", + "variationType": "string", + "variationValue": "two", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-03": { + "allocationKey": "alloc-l", + "variationKey": "v3", + "variationType": "integer", + "variationValue": 3, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-04": { + "allocationKey": "alloc-l", + "variationKey": "v4", + "variationType": "boolean", + "variationValue": false, + "reason": "DEFAULT", + "doLog": true + }, + "fixture.large.flag-05": { + "allocationKey": "alloc-l", + "variationKey": "v5", + "variationType": "string", + "variationValue": "five", + "reason": "SPLIT", + "doLog": true + }, + "fixture.large.flag-06": { + "allocationKey": "alloc-l", + "variationKey": "v6", + "variationType": "float", + "variationValue": 6.6, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "fixture.large.flag-07": { + "allocationKey": "alloc-l", + "variationKey": "v7", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-08": { + "allocationKey": "alloc-l", + "variationKey": "v8", + "variationType": "string", + "variationValue": "eight", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-09": { + "allocationKey": "alloc-l", + "variationKey": "v9", + "variationType": "integer", + "variationValue": 9, + "reason": "DEFAULT", + "doLog": true + }, + "fixture.large.flag-10": { + "allocationKey": "alloc-l", + "variationKey": "v10", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-11": { + "allocationKey": "alloc-l", + "variationKey": "v11", + "variationType": "string", + "variationValue": "eleven", + "reason": "SPLIT", + "doLog": true + }, + "fixture.large.flag-12": { + "allocationKey": "alloc-l", + "variationKey": "v12", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-13": { + "allocationKey": "alloc-l", + "variationKey": "v13", + "variationType": "boolean", + "variationValue": false, + "reason": "DEFAULT", + "doLog": false + }, + "fixture.large.flag-14": { + "allocationKey": "alloc-l", + "variationKey": "v14", + "variationType": "string", + "variationValue": "fourteen", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-15": { + "allocationKey": "alloc-l", + "variationKey": "v15", + "variationType": "integer", + "variationValue": 15, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-16": { + "allocationKey": "alloc-l", + "variationKey": "v16", + "variationType": "boolean", + "variationValue": true, + "reason": "SPLIT", + "doLog": true + }, + "fixture.large.flag-17": { + "allocationKey": "alloc-l", + "variationKey": "v17", + "variationType": "string", + "variationValue": "seventeen", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-18": { + "allocationKey": "alloc-l", + "variationKey": "v18", + "variationType": "float", + "variationValue": 18.0, + "reason": "DEFAULT", + "doLog": true + }, + "fixture.large.flag-19": { + "allocationKey": "alloc-l", + "variationKey": "v19", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.large.flag-20": { + "allocationKey": "alloc-l", + "variationKey": "v20", + "variationType": "object", + "variationValue": { + "count": 20 + }, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.021": { + "allocationKey": "alloc-21", + "variationKey": "var-21", + "variationType": "boolean", + "variationValue": true, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.022": { + "allocationKey": "alloc-22", + "variationKey": "var-22", + "variationType": "string", + "variationValue": "value", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.023": { + "allocationKey": "alloc-23", + "variationKey": "var-23", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.024": { + "allocationKey": "alloc-24", + "variationKey": "var-24", + "variationType": "float", + "variationValue": 1.5, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.025": { + "allocationKey": "alloc-25", + "variationKey": "var-25", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.026": { + "allocationKey": "alloc-26", + "variationKey": "var-26", + "variationType": "string", + "variationValue": "value", + "reason": "SPLIT", + "doLog": true + }, + "flag.set.027": { + "allocationKey": "alloc-27", + "variationKey": "var-27", + "variationType": "integer", + "variationValue": 42, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.028": { + "allocationKey": "alloc-28", + "variationKey": "var-28", + "variationType": "float", + "variationValue": 1.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.029": { + "allocationKey": "alloc-29", + "variationKey": "var-29", + "variationType": "boolean", + "variationValue": true, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.030": { + "allocationKey": "alloc-30", + "variationKey": "var-30", + "variationType": "string", + "variationValue": "value", + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.031": { + "allocationKey": "alloc-31", + "variationKey": "var-31", + "variationType": "integer", + "variationValue": 42, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.032": { + "allocationKey": "alloc-32", + "variationKey": "var-32", + "variationType": "float", + "variationValue": 1.5, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.033": { + "allocationKey": "alloc-33", + "variationKey": "var-33", + "variationType": "boolean", + "variationValue": true, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.034": { + "allocationKey": "alloc-34", + "variationKey": "var-34", + "variationType": "string", + "variationValue": "value", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.035": { + "allocationKey": "alloc-35", + "variationKey": "var-35", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.036": { + "allocationKey": "alloc-36", + "variationKey": "var-36", + "variationType": "float", + "variationValue": 1.5, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.037": { + "allocationKey": "alloc-37", + "variationKey": "var-37", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.038": { + "allocationKey": "alloc-38", + "variationKey": "var-38", + "variationType": "string", + "variationValue": "value", + "reason": "SPLIT", + "doLog": true + }, + "flag.set.039": { + "allocationKey": "alloc-39", + "variationKey": "var-39", + "variationType": "integer", + "variationValue": 42, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.040": { + "allocationKey": "alloc-40", + "variationKey": "var-40", + "variationType": "float", + "variationValue": 1.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.041": { + "allocationKey": "alloc-41", + "variationKey": "var-41", + "variationType": "boolean", + "variationValue": true, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.042": { + "allocationKey": "alloc-42", + "variationKey": "var-42", + "variationType": "string", + "variationValue": "value", + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.043": { + "allocationKey": "alloc-43", + "variationKey": "var-43", + "variationType": "integer", + "variationValue": 42, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.044": { + "allocationKey": "alloc-44", + "variationKey": "var-44", + "variationType": "float", + "variationValue": 1.5, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.045": { + "allocationKey": "alloc-45", + "variationKey": "var-45", + "variationType": "boolean", + "variationValue": true, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.046": { + "allocationKey": "alloc-46", + "variationKey": "var-46", + "variationType": "string", + "variationValue": "value", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.047": { + "allocationKey": "alloc-47", + "variationKey": "var-47", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + }, + "flag.set.048": { + "allocationKey": "alloc-48", + "variationKey": "var-48", + "variationType": "float", + "variationValue": 1.5, + "reason": "DEFAULT", + "doLog": false + }, + "flag.set.049": { + "allocationKey": "alloc-49", + "variationKey": "var-49", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "flag.set.050": { + "allocationKey": "alloc-50", + "variationKey": "var-50", + "variationType": "string", + "variationValue": "value", + "reason": "SPLIT", + "doLog": true + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multiple-reasons.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multiple-reasons.json new file mode 100644 index 000000000..6d507829a --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/multiple-reasons.json @@ -0,0 +1,91 @@ +{ + "name": "multiple-reasons", + "description": "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": { + "fixture.reason.targeted": { + "allocationKey": "alloc-target", + "variationKey": "targeted-on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.reason.default": { + "allocationKey": "alloc-default", + "variationKey": "default-val", + "variationType": "string", + "variationValue": "baseline", + "reason": "DEFAULT", + "doLog": true + }, + "fixture.reason.split": { + "allocationKey": "alloc-split", + "variationKey": "bucket-a", + "variationType": "integer", + "variationValue": 42, + "reason": "SPLIT", + "doLog": true + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json new file mode 100644 index 000000000..4bbd9d3a7 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json @@ -0,0 +1,291 @@ +{ + "name": "numeric-float", + "description": "numeric float.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "float-0-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-0-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float-1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "float--1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "evaluations": [ + { + "flag": "float-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 5, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json new file mode 100644 index 000000000..381cfa53a --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json @@ -0,0 +1,195 @@ +{ + "name": "numeric-integer", + "description": "numeric integer.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "integer-0-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer-0-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer-5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer-5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer--5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "integer--5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "evaluations": [ + { + "flag": "integer-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 6, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 3, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json new file mode 100644 index 000000000..946151c60 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json @@ -0,0 +1,261 @@ +{ + "name": "numeric-number", + "description": "numeric number.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "number-0-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-0-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--5-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--5-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number-1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--1.25-as-integer": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "number--1.25-as-float": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + } + } + } + }, + "evaluations": [ + { + "flag": "number-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/object-nested-deep.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/object-nested-deep.json new file mode 100644 index 000000000..86bd89aab --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/object-nested-deep.json @@ -0,0 +1,87 @@ +{ + "name": "object-nested-deep", + "description": "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": { + "fixture.config.deep": { + "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 + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/special-characters-in-keys.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/special-characters-in-keys.json new file mode 100644 index 000000000..00a4666dd --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/special-characters-in-keys.json @@ -0,0 +1,108 @@ +{ + "name": "special-characters-in-keys", + "description": "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": { + "com.example.feature.enabled": { + "allocationKey": "alloc-dot", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "my-team--feature-toggle": { + "allocationKey": "alloc-hyphen", + "variationKey": "active", + "variationType": "string", + "variationValue": "yes", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "legacy_flag_name_v2": { + "allocationKey": "alloc-underscore", + "variationKey": "v2-val", + "variationType": "integer", + "variationValue": 100, + "reason": "DEFAULT", + "doLog": true + }, + "mix.ed-key_style.v1": { + "allocationKey": "alloc-mixed", + "variationKey": "mixed-val", + "variationType": "float", + "variationValue": 2.718, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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 + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json new file mode 100644 index 000000000..2fd77213f --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json @@ -0,0 +1,144 @@ +{ + "name": "type-mismatch-all-types", + "description": "Wrong typed getters return the caller default with TYPE_MISMATCH. APIs with one Number getter do not implement integer-versus-float mismatch.", + "context": { + "targetingKey": "mismatch-user", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "fixture.mismatch.bool-as-string": { + "allocationKey": "alloc-mm", + "variationKey": "var-bool", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.mismatch.string-as-int": { + "allocationKey": "alloc-mm", + "variationKey": "var-str", + "variationType": "string", + "variationValue": "hello", + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.mismatch.int-as-float": { + "allocationKey": "alloc-mm", + "variationKey": "var-int", + "variationType": "integer", + "variationValue": 7, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.mismatch.float-as-bool": { + "allocationKey": "alloc-mm", + "variationKey": "var-float", + "variationType": "float", + "variationValue": 3.14, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fixture.mismatch.object-as-string": { + "allocationKey": "alloc-mm", + "variationKey": "var-obj", + "variationType": "object", + "variationValue": { + "key": "value" + }, + "reason": "TARGETING_MATCH", + "doLog": true + } + } + } + } + }, + "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": 0.0, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter." + } + ] + }, + { + "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, + "evaluationEvents": [ + { + "_count": 5, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-keys.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-keys.json new file mode 100644 index 000000000..9c1be0a32 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json new file mode 100644 index 000000000..6547f39aa --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json @@ -0,0 +1,310 @@ +{ + "name": "obfuscation-numeric-float", + "description": "numeric float.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "4fc70afd0edbe9f6c3605f50b73892f4167f395a93aa5c2e91c5380818270237": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "01c7520392b183ba08426484c8981ef7b9574d8d5e1f0b5b0afc04945b1fdc65": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "30a0b6a28e18bbdd712d677df8b0a0012086219260528d4967214fa45f36f90b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "b432d3d8d83d0f10acfb8d8099c7dd1238b96b45e12f2407ae6bfd938ad92354": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "26a957788e3e02fda8ace878f452615d85c2d80a539a19522b45c9fac903b905": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "fd7b22e26bc6227562127dc3945e30e159e1b522ba003b52706d5b33c8db0519": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "93cafc55f2224734ccd9a3a0030a8bd007dcded0f48320a16d2044744cc6a4f6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "5ad25a909e7b8ef40cf13c71a7ec8f2ab4bb8a3ae4a5439029357f9982508bf8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "4d677ea458fe5ed9851dbdba4f92981a432b5b9b467445032483b9fa2d2f7368": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "eb98f328fb6978a74722d54badcaf0335bc7433ca86e205ecca0be0ac86012c8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "float-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "float--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "float--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 5, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json new file mode 100644 index 000000000..7380abfe8 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json @@ -0,0 +1,214 @@ +{ + "name": "obfuscation-numeric-integer", + "description": "numeric integer.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "3a56e171b0efb081585be54227ac1792ebae79900fb0e0ee2c3850ebc7dd7fd0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "3df507231bc602ed78f6e01db4497a88b33c086a455deba043f35dde999c0b7d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "58e7c159ddb85fe9250395e7797d80ecb7dca88e1a22767261a9482611baa358": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "a4ef458efa0a3876a8509351d70fa007a68460b21c64cc64680c1099ec450dab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "01c7f586a7f7bd98d4cbc75760f506b6ff8a9ba7195e88f975a708cc7b6ebb30": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "14a559f9d240120a5038b7f3f6529a0cba11d3cbc84409c84678290f4116b809": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "integer-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "integer--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "integer--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 6, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 3, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json new file mode 100644 index 000000000..9e54ced94 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json @@ -0,0 +1,280 @@ +{ + "name": "obfuscation-numeric-number", + "description": "numeric number.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "085fa5b25a3d42952668a59d052deb6ee5bc2418a352b9887556fe862ef78ca8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "66aeb85f02e96149bd485b28375161c4bc154e748545593a40da549d53f479c2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 0, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "eef7048e9d655ec6f29019eec43986e1d4ba6ff42523ecd8f46b6ac0ae883050": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "a46a4fdb29e1de92315dc1ee481f59a33455841d317e38191f13cdcdd9fd2eb0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "5f25ccca362e35bf27475aa056da166b4e7ecf6ed66b65bc25f9d573717d2628": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "914ae5836aae14f5bd8348d1669e008e400c43887d08cd6dc67b028d914e8c6a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -5, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "3cecd82b1b8e3c49bc3fe1886fdd0a57de07ac68a437f59bf7c6c6317d83b03b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "5a539f4ac96a34becffeed98230a919b3082e085bb79deed40d88600d5500636": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "ec97872ee38dc677992d1df24af2093298d35a35d246ea3dd39df7e33279b358": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + }, + "4b33526589906dcabeefa0e880ad6697d4e6673af26187c67947124777a67bbc": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -1.25, + "reason": "TARGETING_MATCH", + "doLog": false + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "000102030405060708090a0b0c0d0e0f" + } + } + } + }, + "evaluations": [ + { + "flag": "number-0-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-0-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--5-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number-1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number-1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "number--1.25-as-integer", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_skip": [ + { + "platform": "web", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + }, + { + "platform": "react-native", + "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + } + ] + }, + { + "flag": "number--1.25-as-float", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -1.25, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [], + "evaluationEvents": [ + { + "_count": 10, + "_skip": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + }, + { + "_count": 8, + "_include": [ + { + "platform": "web", + "reason": "Single Number API." + }, + { + "platform": "react-native", + "reason": "Single Number API." + } + ] + } + ] + }, + "_include": [ + { + "platform": "ios", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "android", + "reason": "Implements assignment-encoding-flag-key-256-v1." + }, + { + "platform": "flutter", + "reason": "Implements assignment-encoding-flag-key-256-v1." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-false-with-descriptor.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-false-with-descriptor.json new file mode 100644 index 000000000..40a036689 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-descriptor.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-descriptor.json new file mode 100644 index 000000000..0c0d8633f --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-marker.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-marker.json new file mode 100644 index 000000000..18741990f --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-salt.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-missing-salt.json new file mode 100644 index 000000000..ce837948c --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-nonhex-digest.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-nonhex-digest.json new file mode 100644 index 000000000..1cbb1a4e6 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-nonhex-salt.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-nonhex-salt.json new file mode 100644 index 000000000..1c2c2ffbb --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-descriptor.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-descriptor.json new file mode 100644 index 000000000..e1d273588 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-marker.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-marker.json new file mode 100644 index 000000000..67a35537e --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-salt.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-null-salt.json new file mode 100644 index 000000000..84350ae9e --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-plaintext.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-plaintext.json new file mode 100644 index 000000000..94059a265 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-short-digest.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-short-digest.json new file mode 100644 index 000000000..0e2f913c1 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-short-salt.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-short-salt.json new file mode 100644 index 000000000..2b54a3938 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-string-marker.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-string-marker.json new file mode 100644 index 000000000..1c1a8bf92 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-unknown-scheme.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-unknown-scheme.json new file mode 100644 index 000000000..7e98d9e89 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-uppercase-digest.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-uppercase-digest.json new file mode 100644 index 000000000..4d53d3e8c --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-uppercase-salt.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-reject-uppercase-salt.json new file mode 100644 index 000000000..2d89321ff --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-rotated-salt.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-rotated-salt.json new file mode 100644 index 000000000..ba16c9a0c --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-unsupported-consumer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-unsupported-consumer.json new file mode 100644 index 000000000..94894f1bc --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/index.json b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json new file mode 100644 index 000000000..336c33dbb --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json @@ -0,0 +1,39 @@ +[ + "cases/emissions/all-doLog-false.json", + "cases/emissions/duplicate-evaluation.json", + "cases/evaluator/all-types-success.json", + "cases/evaluator/defaults-and-emission-gates.json", + "cases/evaluator/empty-flags-response.json", + "cases/evaluator/falsy-values.json", + "cases/evaluator/integer-precision-native-only.json", + "cases/evaluator/multi-flag-set.json", + "cases/evaluator/multiple-reasons.json", + "cases/evaluator/numeric-float.json", + "cases/evaluator/numeric-integer.json", + "cases/evaluator/numeric-number.json", + "cases/evaluator/object-nested-deep.json", + "cases/evaluator/special-characters-in-keys.json", + "cases/evaluator/type-mismatch-all-types.json", + "cases/obfuscation/obfuscation-keys.json", + "cases/obfuscation/obfuscation-numeric-float.json", + "cases/obfuscation/obfuscation-numeric-integer.json", + "cases/obfuscation/obfuscation-numeric-number.json", + "cases/obfuscation/obfuscation-reject-false-with-descriptor.json", + "cases/obfuscation/obfuscation-reject-missing-descriptor.json", + "cases/obfuscation/obfuscation-reject-missing-marker.json", + "cases/obfuscation/obfuscation-reject-missing-salt.json", + "cases/obfuscation/obfuscation-reject-nonhex-digest.json", + "cases/obfuscation/obfuscation-reject-nonhex-salt.json", + "cases/obfuscation/obfuscation-reject-null-descriptor.json", + "cases/obfuscation/obfuscation-reject-null-marker.json", + "cases/obfuscation/obfuscation-reject-null-salt.json", + "cases/obfuscation/obfuscation-reject-plaintext.json", + "cases/obfuscation/obfuscation-reject-short-digest.json", + "cases/obfuscation/obfuscation-reject-short-salt.json", + "cases/obfuscation/obfuscation-reject-string-marker.json", + "cases/obfuscation/obfuscation-reject-unknown-scheme.json", + "cases/obfuscation/obfuscation-reject-uppercase-digest.json", + "cases/obfuscation/obfuscation-reject-uppercase-salt.json", + "cases/obfuscation/obfuscation-rotated-salt.json", + "cases/obfuscation/obfuscation-unsupported-consumer.json" +] diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/schema.json b/packages/core/src/flags/__tests__/fixtures/precomputed/schema.json new file mode 100644 index 000000000..5aac5dce2 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/schema.json @@ -0,0 +1,400 @@ +{ + "$id": "precomputed-assignment", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "FLEX Precomputed Assignment Fixture", + "description": "Schema for precomputed assignment test fixtures consumed by client SDKs.", + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "description", + "context", + "response", + "evaluations", + "expectations" + ], + "properties": { + "name": { + "type": "string", + "description": "Fixture identifier; should match the filename stem." + }, + "description": { + "type": "string", + "description": "Human-readable description of what the fixture tests." + }, + "context": { + "$ref": "#/$defs/EvaluationContext" + }, + "response": { + "type": "object", + "description": "Mocked precomputed-assignments response. Open-ended; downstream code validates the payload shape." + }, + "evaluations": { + "type": "array", + "items": { + "$ref": "#/$defs/Evaluation" + } + }, + "expectations": { + "$ref": "#/$defs/Expectations" + }, + "_skip": { + "$ref": "#/$defs/PlatformFilter", + "description": "Skip this entire fixture for listed platforms." + }, + "_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" + ] + }, + "$defs": { + "PlatformFilterEntry": { + "type": "object", + "required": [ + "platform" + ], + "additionalProperties": false, + "properties": { + "platform": { + "type": "string", + "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", + "description": "Optional explanation for why this platform is skipped or included." + } + } + }, + "PlatformFilter": { + "type": "array", + "items": { + "$ref": "#/$defs/PlatformFilterEntry" + }, + "minItems": 1 + }, + "EvaluationContext": { + "type": "object", + "required": [ + "targetingKey", + "attributes" + ], + "additionalProperties": false, + "properties": { + "targetingKey": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": "object" + } + } + }, + "ExpectedResult": { + "type": "object", + "required": [ + "value" + ], + "additionalProperties": false, + "properties": { + "value": { + "description": "Expected flag value; must match the evaluation variationType." + }, + "reason": { + "type": [ + "string", + "null" + ], + "description": "Expected OpenFeature reason code." + }, + "errorCode": { + "type": [ + "string", + "null" + ], + "description": "Expected OpenFeature error code." + }, + "variantKey": { + "type": [ + "string", + "null" + ], + "description": "Expected variation key." + } + } + }, + "Evaluation": { + "type": "object", + "required": [ + "flag", + "variationType", + "defaultValue", + "expectedResult" + ], + "additionalProperties": false, + "properties": { + "flag": { + "type": "string", + "description": "Flag key to evaluate." + }, + "variationType": { + "type": "string", + "enum": [ + "boolean", + "string", + "integer", + "float", + "object" + ], + "description": "Type to request from the SDK getter." + }, + "defaultValue": { + "description": "Default value passed to the getter; must match variationType." + }, + "expectedResult": { + "$ref": "#/$defs/ExpectedResult" + }, + "_skip": { + "$ref": "#/$defs/PlatformFilter" + }, + "_include": { + "$ref": "#/$defs/PlatformFilter" + } + }, + "allOf": [ + { + "if": { + "properties": { + "variationType": { + "const": "boolean" + } + }, + "required": [ + "variationType" + ] + }, + "then": { + "properties": { + "defaultValue": { + "type": "boolean" + }, + "expectedResult": { + "properties": { + "value": { + "type": "boolean" + } + } + } + } + } + }, + { + "if": { + "properties": { + "variationType": { + "const": "string" + } + }, + "required": [ + "variationType" + ] + }, + "then": { + "properties": { + "defaultValue": { + "type": "string" + }, + "expectedResult": { + "properties": { + "value": { + "type": "string" + } + } + } + } + } + }, + { + "if": { + "properties": { + "variationType": { + "const": "integer" + } + }, + "required": [ + "variationType" + ] + }, + "then": { + "properties": { + "defaultValue": { + "type": "integer" + }, + "expectedResult": { + "properties": { + "value": { + "type": "integer" + } + } + } + } + } + }, + { + "if": { + "properties": { + "variationType": { + "const": "float" + } + }, + "required": [ + "variationType" + ] + }, + "then": { + "properties": { + "defaultValue": { + "type": "number" + }, + "expectedResult": { + "properties": { + "value": { + "type": "number" + } + } + } + } + } + }, + { + "if": { + "properties": { + "variationType": { + "const": "object" + } + }, + "required": [ + "variationType" + ] + }, + "then": { + "properties": { + "defaultValue": { + "type": "object" + }, + "expectedResult": { + "properties": { + "value": { + "type": "object" + } + } + } + } + } + } + ], + "not": { + "required": [ + "_skip", + "_include" + ] + } + }, + "EventMatcher": { + "type": "object", + "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": { + "type": "integer", + "minimum": 1, + "description": "Number of events this matcher should consume." + }, + "_skip": { + "$ref": "#/$defs/PlatformFilter" + }, + "_include": { + "$ref": "#/$defs/PlatformFilter" + } + }, + "not": { + "required": [ + "_skip", + "_include" + ] + } + }, + "Expectations": { + "type": "object", + "additionalProperties": false, + "properties": { + "noUnmatchedEvents": { + "type": "boolean", + "default": false, + "description": "When true, assert that all received events (exposures and evaluations) are consumed by matchers. No extra events allowed." + }, + "exposures": { + "type": "array", + "items": { + "$ref": "#/$defs/EventMatcher" + }, + "description": "Ordered, exclusive matchers for exposure events." + }, + "evaluationEvents": { + "type": "array", + "items": { + "$ref": "#/$defs/EventMatcher" + }, + "description": "Ordered, exclusive matchers for flagevaluation events." + } + } + } + }, + "allOf": [ + { + "if": { + "required": [ + "expectedResponseError" + ] + }, + "then": { + "properties": { + "evaluations": { + "maxItems": 0 + } + } + }, + "else": { + "properties": { + "evaluations": { + "minItems": 1 + } + } + } + } + ] +} diff --git a/packages/core/src/flags/configuration/precomputed.ts b/packages/core/src/flags/configuration/precomputed.ts index d0c60403a..8e67e7d70 100644 --- a/packages/core/src/flags/configuration/precomputed.ts +++ b/packages/core/src/flags/configuration/precomputed.ts @@ -48,7 +48,15 @@ export const decodePrecomputedFlags = ( // carries it. Read it defensively so obfuscated payloads are still rejected: // de-hashing keys / decoding values is not implemented here, so fail predictably // instead of mis-mapping hashed keys. - if ((attributes as { obfuscated?: boolean } | undefined)?.obfuscated) { + const encoding = attributes as + | { obfuscated?: unknown; obfuscation?: unknown } + | undefined; + if ( + encoding && + (Object.prototype.hasOwnProperty.call(encoding, 'obfuscation') || + (Object.prototype.hasOwnProperty.call(encoding, 'obfuscated') && + encoding.obfuscated !== false)) + ) { throw new UnsupportedConfigurationError( 'Obfuscated precomputed configurations are not supported.' ); From df25fb8088cba87589591d80bff7ed76addd6ec4 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:34:05 -0500 Subject: [PATCH 2/8] FFL-3568: Import upstream serial-ID response coverage --- packages/core/README.md | 4 + .../__tests__/CanonicalPrecomputed.test.ts | 4 + .../fixtures/precomputed/SOURCE.json | 5 +- .../emissions/serial-id-on-exposure.json | 109 ++++++++++++++++++ .../__tests__/fixtures/precomputed/index.json | 1 + .../src/flags/configuration/precomputed.ts | 10 +- 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/serial-id-on-exposure.json diff --git a/packages/core/README.md b/packages/core/README.md index 61536acd1..1e9dd305a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -175,3 +175,7 @@ It uses a short-lived token restricted to the default branch and this workflow. It waits when an update PR is already open. Normal SDK CI must pass before merge. Merge the upstream fixture contract before merging its consumer PRs. The update workflow never merges or publishes an SDK release. + +The JavaScript decoder preserves safe numeric serial IDs as bridge strings, including zero. +The Android bridge can forward them. The pinned iOS SDK (3.16.1) lacks serial-ID support. +End-to-end serial-ID parity on iOS requires a native SDK release and bridge update. diff --git a/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts b/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts index 639641d26..0e0eb0967 100644 --- a/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts +++ b/packages/core/src/flags/__tests__/CanonicalPrecomputed.test.ts @@ -113,6 +113,10 @@ describe(`canonical precompute ${source.commit}`, () => { expect(actual.value).toEqual(expectedResult.value); expect(actual).toMatchObject(expectedResult); if (!details.errorCode) { + const raw = fixture.response.data.attributes.flags[flag]; + const expectedSerialId = + raw.serialId == null ? undefined : String(raw.serialId); + expect(decoded.get(flag)?.serialId).toBe(expectedSerialId); tracked += 1; expect( NativeModules.DdFlags.trackEvaluation diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json index c4da2bf85..f5ddb0506 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json @@ -1,12 +1,13 @@ { "repository": "https://github.com/DataDog/ffe-system-test-data.git", - "commit": "e4f93740f783ddc0823be5f15daaf78c01869d48", + "commit": "23d948da7cd639be738c1a040264b0986574f973", "files": { "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", "NOTICE": "a5245f10dbda39cc65bad11ab3a6022b9e5c05b259da3f7aa0302d932f31083e", "README.md": "0c29b94146aa81088dc0e70435019f6d8bded371a068d566768ecdb2e7cb86e1", "cases/emissions/all-doLog-false.json": "1902637564cde9a7105a80730bfbeb9ce57178a59910c905fad03d7a1118b4de", "cases/emissions/duplicate-evaluation.json": "88c1dbdcf3c1bb1ee8dd9a0842123447921b9d9fe806f26d24cc41a7d73bb0cc", + "cases/emissions/serial-id-on-exposure.json": "83a7504e6658b875ececdd1972c04386130feb31c607ccf7b61cd45ce46c1fc0", "cases/evaluator/all-types-success.json": "ff22e47d5bda8a1fef19892c853e4396ccdb8509a3741888eb69169847fc4fde", "cases/evaluator/defaults-and-emission-gates.json": "fd9c306086b7ce87cacd7f69d2f2c7af03921fffa9b43455dcc89dc17f3c3bfd", "cases/evaluator/empty-flags-response.json": "f9ddb39e6a425b2c3f7884c7bdf92516e5ebac5b0ed4d5b7be6aa5e38950ea99", @@ -42,7 +43,7 @@ "cases/obfuscation/obfuscation-reject-uppercase-salt.json": "65f4a35f6042863cbec5f19634fa9526da1e6c66ddecf3aad488f4455e26ec7e", "cases/obfuscation/obfuscation-rotated-salt.json": "a97bf1f0d57ad66d122a4286433ba216469d8de2824e03b2d79fd860b87e277d", "cases/obfuscation/obfuscation-unsupported-consumer.json": "d632485351b4b6cb7b550769cde55c46960ebee7ac811059a5147f143524554f", - "index.json": "07d8036a97955a47a5ea5e840c7f612f69b2f13bce67ddbd636d2a6dfdde6a43", + "index.json": "84c8751d9be27e2e86662a69de20b01ea273a472a13c6675d6c531af81f1cd8f", "schema.json": "0353d393b1b4193346b2fa907f500b5c60be51318759c3afe52e549f1662195d" } } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/serial-id-on-exposure.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/serial-id-on-exposure.json new file mode 100644 index 000000000..11e9bcfed --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/emissions/serial-id-on-exposure.json @@ -0,0 +1,109 @@ +{ + "name": "serial-id-on-exposure", + "description": "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": { + "serial.id.flag": { + "allocationKey": "holdout-allocation", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true, + "serialId": 340132 + }, + "no.serial.id.flag": { + "allocationKey": "regular-allocation", + "variationKey": "off", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "zero.serial.id.flag": { + "allocationKey": "zero-allocation", + "variationKey": "on", + "variationType": "boolean", + "variationValue": true, + "reason": "TARGETING_MATCH", + "doLog": true, + "serialId": 0 + }, + "null.serial.id.flag": { + "allocationKey": "null-allocation", + "variationKey": "off", + "variationType": "boolean", + "variationValue": false, + "reason": "TARGETING_MATCH", + "doLog": true, + "serialId": null + } + } + } + } + }, + "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} + ] + } +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/index.json b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json index 336c33dbb..dd4179273 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/index.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json @@ -1,6 +1,7 @@ [ "cases/emissions/all-doLog-false.json", "cases/emissions/duplicate-evaluation.json", + "cases/emissions/serial-id-on-exposure.json", "cases/evaluator/all-types-success.json", "cases/evaluator/defaults-and-emission-gates.json", "cases/evaluator/empty-flags-response.json", diff --git a/packages/core/src/flags/configuration/precomputed.ts b/packages/core/src/flags/configuration/precomputed.ts index 8e67e7d70..87d7accba 100644 --- a/packages/core/src/flags/configuration/precomputed.ts +++ b/packages/core/src/flags/configuration/precomputed.ts @@ -115,7 +115,8 @@ const toFlagCacheEntry = ( allocationKey, reason, doLog, - extraLogging + extraLogging, + serialId } = flag as Partial; if ( @@ -160,8 +161,6 @@ const toFlagCacheEntry = ( return null; } - // The released decoder's payload omits serialId. The OpenFeature offline adapter adds it from - // the evaluator's metadata, for both precomputed and rules flags. return { key, value: variationValue, @@ -171,7 +170,10 @@ const toFlagCacheEntry = ( variationValue: stringifyValue(variationValue), reason, doLog, - extraLogging: extraLogging ?? {} + extraLogging: extraLogging ?? {}, + ...(typeof serialId === 'number' && Number.isSafeInteger(serialId) + ? { serialId: String(serialId) } + : {}) }; }; From f0f560f61e05b7e7c9253fce5ba990236e0a160b Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 14:38:41 -0500 Subject: [PATCH 3/8] FFL-3568: Narrow decoded serial ID before validation --- packages/core/src/flags/configuration/precomputed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/flags/configuration/precomputed.ts b/packages/core/src/flags/configuration/precomputed.ts index 87d7accba..69065464e 100644 --- a/packages/core/src/flags/configuration/precomputed.ts +++ b/packages/core/src/flags/configuration/precomputed.ts @@ -115,9 +115,9 @@ const toFlagCacheEntry = ( allocationKey, reason, doLog, - extraLogging, - serialId + extraLogging } = flag as Partial; + const { serialId } = flag as { serialId?: unknown }; if ( typeof variationType !== 'string' || From f9ffb767feb31562781792f0e7918ea95731f981 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 16:19:13 -0500 Subject: [PATCH 4/8] FFL-3568: Apply lossless numeric contract and shared boundaries --- packages/core/README.md | 10 +- .../__tests__/fixtures/precomputed/README.md | 15 +- .../fixtures/precomputed/SOURCE.json | 26 +- .../evaluator/numeric-boundaries-float.json | 1093 ++++++++++++++++ .../evaluator/numeric-boundaries-integer.json | 1093 ++++++++++++++++ .../evaluator/numeric-boundaries-number.json | 1093 ++++++++++++++++ .../cases/evaluator/numeric-float.json | 402 ++++-- .../cases/evaluator/numeric-integer.json | 430 ++++++- .../cases/evaluator/numeric-number.json | 358 +++++- .../evaluator/type-mismatch-all-types.json | 39 +- .../obfuscation-numeric-boundaries-float.json | 1094 +++++++++++++++++ ...bfuscation-numeric-boundaries-integer.json | 1094 +++++++++++++++++ ...obfuscation-numeric-boundaries-number.json | 1094 +++++++++++++++++ .../obfuscation-numeric-float.json | 410 ++++-- .../obfuscation-numeric-integer.json | 438 ++++++- .../obfuscation-numeric-number.json | 366 +++++- .../__tests__/fixtures/precomputed/index.json | 6 + .../__tests__/precomputed.test.ts | 5 +- .../src/flags/configuration/precomputed.ts | 5 +- 19 files changed, 8577 insertions(+), 494 deletions(-) create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-float.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-integer.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-number.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json diff --git a/packages/core/README.md b/packages/core/README.md index 1e9dd305a..d8f3f52ae 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -141,11 +141,11 @@ Adding a case requires an upstream fixture change and snapshot refresh. It requi Missing or empty fixtures fail the suite. Shared `_include` and `_skip` fields describe API applicability. Do not add local exclusions to hide failures. -Native Integer getters accept `integer`. Native Double getters accept `float`. -Legacy `number` accepts Double reads and representable integral Integer reads. -A whole-valued `float` still fails an Integer read with `TYPE_MISMATCH`. -React Native's Number API has no separate integer type. -These rules do not enable a new server wire format. Sending specific numeric types requires compatible capability negotiation. +Numeric getters use lossless conversion regardless of the `integer`, `float`, or `number` wire label. +Integer getters accept whole values within their native range. Double getters reject integer values that lose precision. +Fractional Integer reads, range overflow, and inexact conversions return `TYPE_MISMATCH`. +React Native exposes one Number getter. Its tests exclude cases that require a separate Integer getter or native integer precision. +The service keeps sending `number`. This work adds no numeric capability. Cases cover values, defaults, errors, reasons, variants, emission gates, duplicate evaluations, and numeric boundaries. Obfuscation cases cover fixed digests, salts, Unicode, whitespace, malformed metadata, and plaintext event identity. diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/README.md b/packages/core/src/flags/__tests__/fixtures/precomputed/README.md index 0a0791168..cf76ab45c 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/README.md +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/README.md @@ -180,11 +180,16 @@ python3 ci/validate-precomputed-fixtures.py ## Mobile conformance and numeric types -Native Integer getters accept `integer`. Native Double getters accept `float`. -The declared type controls this decision even when a `float` value is whole. -Legacy `number` accepts Double reads and representable integral Integer reads. -Fractional Integer reads return the default with `TYPE_MISMATCH`. -Single-Number APIs use the explicit evaluation filters for cross-type tests. +Numeric reads depend on the value, not the `integer`, `float`, or `number` label. +Integer getters accept whole values within the accessor range. Double getters +accept numeric values without losing integer precision. Fractional Integer reads, +out-of-range reads, and inexact conversions return the default with `TYPE_MISMATCH`. +No new global safe-integer limit is imposed. React Native and web expose one Number +getter; Integer-only cases use explicit platform filters. Boundary fixtures cover +native 32-bit and 64-bit accessors separately. + +The precompute service retains `number`. No numeric capability is introduced. +See FFL-3568 and FFL-3564 for the lossless conversion decision. Discover cases recursively. Do not maintain a filename list. Fail when the corpus is missing or empty. Report the fixture revision, executed evaluations, diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json index f5ddb0506..eae45353c 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json @@ -1,10 +1,10 @@ { "repository": "https://github.com/DataDog/ffe-system-test-data.git", - "commit": "23d948da7cd639be738c1a040264b0986574f973", + "commit": "0feda21aec6710a5243287fbc58a33ca2f658b16", "files": { "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", "NOTICE": "a5245f10dbda39cc65bad11ab3a6022b9e5c05b259da3f7aa0302d932f31083e", - "README.md": "0c29b94146aa81088dc0e70435019f6d8bded371a068d566768ecdb2e7cb86e1", + "README.md": "9d2d59f1d66752d0c0e3828217fc7593a6ffed6992e7cf6e417a61ca336b8fd1", "cases/emissions/all-doLog-false.json": "1902637564cde9a7105a80730bfbeb9ce57178a59910c905fad03d7a1118b4de", "cases/emissions/duplicate-evaluation.json": "88c1dbdcf3c1bb1ee8dd9a0842123447921b9d9fe806f26d24cc41a7d73bb0cc", "cases/emissions/serial-id-on-exposure.json": "83a7504e6658b875ececdd1972c04386130feb31c607ccf7b61cd45ce46c1fc0", @@ -15,16 +15,22 @@ "cases/evaluator/integer-precision-native-only.json": "96c4bed183bfeeab7b59209dcdf5b36726e417f1035a28198ef95642ea673622", "cases/evaluator/multi-flag-set.json": "b1c76977764c1bc11f430a50d33a06bb33daa60ed0d184547250362559f34a0b", "cases/evaluator/multiple-reasons.json": "812b48f5131814fa7e13b3779b47f8805e59a7b063b5737b17139d01111c0134", - "cases/evaluator/numeric-float.json": "f8ce19ac8b0cf9e5750a9eb54975e056bfa53e358015e6b36e84025e3799eecd", - "cases/evaluator/numeric-integer.json": "9522fe4f68e3b8a48aac3cb2ed60fd254ced98f9b8d29eb61eeaa32ce521388c", - "cases/evaluator/numeric-number.json": "dc3b2b0086f9fb0d24e5e5f41249bbb78a77f5880adf22f05e288e9191a8077a", + "cases/evaluator/numeric-boundaries-float.json": "f986d58267e590f1be98dc9fe4b5151852c9565028469735b30885a6f369eaa9", + "cases/evaluator/numeric-boundaries-integer.json": "4569c35d977fff5c77134b9e0342b740b7a18e4cf01e461f0c8b5ed9c250708b", + "cases/evaluator/numeric-boundaries-number.json": "ab1e965ac0b335bf56442c0bfa262ba62214b3118a1c1cda6512f38b38717294", + "cases/evaluator/numeric-float.json": "d889bf0107d350e47fde325836caf6690c643b779c51fc3812f948e22a8c67ce", + "cases/evaluator/numeric-integer.json": "4d080f862f53c6d7fab3670a3793edb1ddd9e566a68e262e59abfaff689db39d", + "cases/evaluator/numeric-number.json": "cf2aafebf871b57f0f3a74abf0126d6a4f35cb18d78602611ee4419e6f9235d9", "cases/evaluator/object-nested-deep.json": "22f8f0c516cfe0456d34697f29bbf39d7736559e0b188de4671d0fee6545fd3d", "cases/evaluator/special-characters-in-keys.json": "07e24ba1a407ef9604af0bc5ab3f8faafe199dd83db94d96d0e3d689430054ce", - "cases/evaluator/type-mismatch-all-types.json": "1c4eb5a6407cca9ca9c8e7d5fd09d241cb8296cb5b42c9a56432cc278a86933a", + "cases/evaluator/type-mismatch-all-types.json": "0904425126ae64794a8c67d13d4bab759a88abc75c27c99f71c514babb9279a3", "cases/obfuscation/obfuscation-keys.json": "3af487d59d5a7064e0e8e6508343db21689180d7d6e862b055934036e1716643", - "cases/obfuscation/obfuscation-numeric-float.json": "92760c90a0d289fc9c8ae38323bc81b5c6a5608d43a05b7b9c4831c097b971bd", - "cases/obfuscation/obfuscation-numeric-integer.json": "eab193a1eb876522c37eb041e704efa91a00c0c92304d52a65897882e589dc35", - "cases/obfuscation/obfuscation-numeric-number.json": "d05e213350654a8f4fd08883316dd10ed7e2a5be51383b8a3c94fd025027833b", + "cases/obfuscation/obfuscation-numeric-boundaries-float.json": "56b4343a717161047ce32138f564c2558a1da49f37f26b7681f94b3cc0ef5473", + "cases/obfuscation/obfuscation-numeric-boundaries-integer.json": "3f2340a46e9cdecd48d07cc2aacdf66cba66fb192ed300e42dda4bbf50e415d7", + "cases/obfuscation/obfuscation-numeric-boundaries-number.json": "bca42ecb5cbb3a6ba08a6adbfa34690accaf986a07bf30a91b764871db673566", + "cases/obfuscation/obfuscation-numeric-float.json": "57ec0a367eb86a94269e353b7ceb28a83ac96a546a779b36efa115fd50464c69", + "cases/obfuscation/obfuscation-numeric-integer.json": "f3954848aa7776f4aed3d05e23445f0a66d5c5aaa461e526db335d7034dbc015", + "cases/obfuscation/obfuscation-numeric-number.json": "c429b7ed0a5e1131b25b1240739c1b5a17b046f833f7d54d74f60d90642c0c2d", "cases/obfuscation/obfuscation-reject-false-with-descriptor.json": "452e57fbcec5a254c65295014ae11675d406a87567e6887933ba493a14109093", "cases/obfuscation/obfuscation-reject-missing-descriptor.json": "f0105da777c3b35ff10c7082e62ddd1f7ce6a4bc15dae27302f279b18b3bfc29", "cases/obfuscation/obfuscation-reject-missing-marker.json": "4fb270faacdcfe92477120f9bf9ea56f98e1503cf992c72da71ecc6fbca5e0dc", @@ -43,7 +49,7 @@ "cases/obfuscation/obfuscation-reject-uppercase-salt.json": "65f4a35f6042863cbec5f19634fa9526da1e6c66ddecf3aad488f4455e26ec7e", "cases/obfuscation/obfuscation-rotated-salt.json": "a97bf1f0d57ad66d122a4286433ba216469d8de2824e03b2d79fd860b87e277d", "cases/obfuscation/obfuscation-unsupported-consumer.json": "d632485351b4b6cb7b550769cde55c46960ebee7ac811059a5147f143524554f", - "index.json": "84c8751d9be27e2e86662a69de20b01ea273a472a13c6675d6c531af81f1cd8f", + "index.json": "e3fc034a98f756a4283e1a10251800fa30d20deac2268c0193a3682ceeb679e6", "schema.json": "0353d393b1b4193346b2fa907f500b5c60be51318759c3afe52e549f1662195d" } } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-float.json new file mode 100644 index 000000000..c7c4335c2 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-integer.json new file mode 100644 index 000000000..4177018bd --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-boundaries-number.json new file mode 100644 index 000000000..4be914128 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json index 4bbd9d3a7..dd8b3cb87 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json @@ -1,6 +1,6 @@ { "name": "numeric-float", - "description": "numeric float.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,85 +10,101 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "float-0-as-integer": { + "numeric-float-0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-0-as-float": { + "numeric-float-1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-5-as-integer": { + "numeric-float-2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-5-as-float": { + "numeric-float-3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--5-as-integer": { + "numeric-float-4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--5-as-float": { + "numeric-float-5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-1.25-as-integer": { + "numeric-float-6": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float-1.25-as-float": { + "numeric-float-7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--1.25-as-integer": { + "numeric-float-8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "float--1.25-as-float": { + "numeric-float-9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "numeric-float-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-float-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } } } @@ -96,28 +112,18 @@ }, "evaluations": [ { - "flag": "float-0-as-integer", + "flag": "numeric-float-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-0-as-float", + "flag": "numeric-float-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -128,28 +134,18 @@ } }, { - "flag": "float-5-as-integer", + "flag": "numeric-float-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-5-as-float", + "flag": "numeric-float-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -160,7 +156,29 @@ } }, { - "flag": "float--5-as-integer", + "flag": "numeric-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-float-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -169,30 +187,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--5-as-float", + "flag": "numeric-float-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float-1.25-as-integer", + "flag": "numeric-float-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -201,30 +227,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float-1.25-as-float", + "flag": "numeric-float-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": 1.25, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float--1.25-as-integer", + "flag": "numeric-float-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -233,23 +267,31 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--1.25-as-float", + "flag": "numeric-float-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -258,33 +300,203 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], + "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": 10, - "_skip": [ + "_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": "web", - "reason": "Single Number API." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true }, { - "_count": 5, + "_count": 1, + "flag": { + "key": "numeric-float-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-float-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json index 381cfa53a..e1ce07409 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json @@ -1,6 +1,6 @@ { "name": "numeric-integer", - "description": "numeric integer.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,53 +10,101 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "integer-0-as-integer": { + "numeric-integer-0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer-0-as-float": { + "numeric-integer-1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer-5-as-integer": { + "numeric-integer-2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer-5-as-float": { + "numeric-integer-3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer--5-as-integer": { + "numeric-integer-4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "integer--5-as-float": { + "numeric-integer-5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "numeric-integer-6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-7": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-9": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-integer-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } } } @@ -64,7 +112,7 @@ }, "evaluations": [ { - "flag": "integer-0-as-integer", + "flag": "numeric-integer-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -75,120 +123,380 @@ } }, { - "flag": "integer-0-as-float", + "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": -99.5, + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer-5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "numeric-integer-7", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": 5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer-5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "numeric-integer-8", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer--5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "numeric-integer-9", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer--5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "numeric-integer-10", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "flag": "numeric-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } } ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], + "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": 6, - "_skip": [ + "_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": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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": 3, + "_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": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "numeric-integer-11" + }, + "evaluation_count": 1 } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json index 946151c60..c97208a84 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json @@ -1,6 +1,6 @@ { "name": "numeric-number", - "description": "numeric number.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,85 +10,101 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "number-0-as-integer": { + "numeric-number-0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-0-as-float": { + "numeric-number-1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-5-as-integer": { + "numeric-number-2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-5-as-float": { + "numeric-number-3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--5-as-integer": { + "numeric-number-4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--5-as-float": { + "numeric-number-5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-1.25-as-integer": { + "numeric-number-6": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number-1.25-as-float": { + "numeric-number-7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--1.25-as-integer": { + "numeric-number-8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "number--1.25-as-float": { + "numeric-number-9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "numeric-number-10": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "numeric-number-11": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } } } @@ -96,7 +112,7 @@ }, "evaluations": [ { - "flag": "number-0-as-integer", + "flag": "numeric-number-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -107,7 +123,7 @@ } }, { - "flag": "number-0-as-float", + "flag": "numeric-number-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -118,7 +134,7 @@ } }, { - "flag": "number-5-as-integer", + "flag": "numeric-number-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -129,7 +145,7 @@ } }, { - "flag": "number-5-as-float", + "flag": "numeric-number-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -140,7 +156,7 @@ } }, { - "flag": "number--5-as-integer", + "flag": "numeric-number-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -151,7 +167,7 @@ } }, { - "flag": "number--5-as-float", + "flag": "numeric-number-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -162,7 +178,7 @@ } }, { - "flag": "number-1.25-as-integer", + "flag": "numeric-number-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -171,19 +187,27 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number-1.25-as-float", + "flag": "numeric-number-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -194,7 +218,47 @@ } }, { - "flag": "number--1.25-as-integer", + "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": { @@ -203,23 +267,31 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number--1.25-as-float", + "flag": "numeric-number-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -228,33 +300,203 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], + "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": 10, - "_skip": [ + "_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": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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": 8, + "_count": 1, + "flag": { + "key": "numeric-number-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "numeric-number-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json index 2fd77213f..801ecd1a4 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/type-mismatch-all-types.json @@ -1,6 +1,6 @@ { "name": "type-mismatch-all-types", - "description": "Wrong typed getters return the caller default with TYPE_MISMATCH. APIs with one Number getter do not implement integer-versus-float mismatch.", + "description": "Wrong nonnumeric getters return TYPE_MISMATCH; an exact numeric conversion succeeds.", "context": { "targetingKey": "mismatch-user", "attributes": {} @@ -84,21 +84,11 @@ "variationType": "float", "defaultValue": 0.0, "expectedResult": { - "value": 0.0, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter." - } - ] + "value": 7, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "var-int" + } }, { "flag": "fixture.mismatch.float-as-bool", @@ -125,19 +115,14 @@ ], "expectations": { "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1 + } + ], "evaluationEvents": [ { - "_count": 5, - "_skip": [ - { - "platform": "web", - "reason": "Single Number API." - }, - { - "platform": "react-native", - "reason": "Single Number API." - } - ] + "_count": 5 } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json new file mode 100644 index 000000000..4ad1d6942 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json @@ -0,0 +1,1094 @@ +{ + "name": "obfuscation-numeric-boundaries-float", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "2aec5b3d4477769702ec52c42349be07a31c413ebffb69196b762523adf205ab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ccd840e29b0846bbc8ace7da7bc20c300b2a14e1f5f68961f2fb76e69fb0ba04": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ebe608c5e7ab0bf15e0c6585810759616ab5881130d7ddb956ec8749c43a0939": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1f2de3a9ac37932ca8fbeaafe88046f8f7a49075c87eec5b805a0967706685a4": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "431adeb8d8d06d55e943812fd5cf8bf6095858eb898fd3133999c3d163607b3d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2c7494350d45235245c36589fe36990e14f5e3eca4522e7c93b05ec4e4e3de7c": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "19b8019e6dc73fdabe4884fd109c0c0865eb682b5a6e9b2c098d8698cb8a25bf": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "05dce5b4f6ec9f670d767a9e2f3095c91dc5f947c0cf65f26ffe0d34d169c27a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "b31d785097e2b21ff4203b7f42d9601f0fe117e04ae52fd41c38ec226ee7c9dd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2561c2c5e59e5ee958e65c5ffc5afdc82deab8caacc24b7fd955426d38f3f10a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a4378fa74480657ce942442b4755051dfe8254b3a869ef1d09e73b70905931ba": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4c973da42e257593047a0e547423acae08bcb5d6529670d6917dc653d559878d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6291d2c93c31947490a82ac572b34ff0a2cfc6d1bf63ee929bd4c427e4c14e46": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ed9c529fd1165fc1f8560f540dd0741dd240473987473db250c027486ca54481": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "87217664c8cdba2054ae7ba5e0513f1fe0932a93deb52ee46c8c4856db50bfaf": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "435af2981e1d1cf3e2e762b9fbb1b99f4850d1b0d4bed9de0f44f0418b223506": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "608602ea98e8c47ffb9f86e51e8093daa81ff902841d774092b32df7f92421e2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ae43ae724ec81be659b9cbd89315102b127b1b9e24e26def87496eb4a8aa38ab": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "2358b34f81306a3acd892893294abea0c33721a65b06e355cda36c8e54739be5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9fc154f59fba732da1a2be979ec48044cd88f185fd90b9cfc306e53f3f6d6a88": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "350edcb8f9921229dea893c8a3948457281d0ef88537c3c8905a4dd924165228": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3ff68a68d0c6c04348089c33052aad256a51086ae365496d7ecf89a55c9a4d70": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00112233445566778899aabbccddeeff" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-float-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-float-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-float-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-float-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-1" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-3" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-21" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-float-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_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." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json new file mode 100644 index 000000000..9f6a4ef37 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json @@ -0,0 +1,1094 @@ +{ + "name": "obfuscation-numeric-boundaries-integer", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "7c5c55bbc1d947ebd29d4d22bbb8e854fb84a3168ec16c9b88ff1eb8048f1259": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1db93bef085224805fc0b94e254d8efea2ad144e255512c7822ba1941efd37ca": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c65f42d21a69e9e63e768bb7c353ccc659d8fa7fda19d552c46571fe747939a1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "57ea520510c0e0ffdf6c016032a1ad8fd7e930feddb56e512c5ba5e176bb747e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3bae488ac7f7c5e64e8d80c5e5d3a01fede98ee9dc1dbfaaa1602bdb834e3199": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "d7e31a5af23eaed253fbab5d5ba775a49cc869265a762861623b9850ca45200e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e214fbcdc5fa2e6b53e80a737cf3b01bf720e1da6bdc8d1a8126c82fae131cc5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f87c7ffcbfc101c063bc2f6533d4a73054a4c3e0cc59b865b6d9a738cded6776": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "24e2195a49630a3b88efcb5227d7044651ffa1e483006af5ef3e41db492ca96a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "45316309ea93c9d4d63ebd704488bd9b862cb9d30303b54ccbc668a45f1a9cfd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "107e31005fc784e93f438721722b248ed66b8ed91d13eefca267786102fe5a81": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ca603c439f5fa9a01c76f4febf58bf0b1034e100dd37fd1731c05f667409846e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bf801a713f62854431659efc54a0cb312dd601a8459d4390b13840fd7f414a02": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6c6bc2ace96267f8a839f207457b3d12b93f8211e7ce9a2f4ef9b2ddfc70412b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "cd01bfe31bd7117a646dddc5be9732400e8ee0076cafd79c189115b8b3081950": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fe4005d2eb3448257136861a88114cadcc85f06cb2ede14a36b581d4900e4a5e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fb7e52e4c272d1e4e9602339c4f6a7094257194ea28d274314b8ca0a4aec639b": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "de7cefc4a649b9c39f7fe31b49d5fa5f8ac6b792e2227717e0130dd127bbfd70": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e084557c149a57fcf7e04041ad080c04586380baa9093a16dfee198a1ad51989": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "f163106c94e153c44a0f9c882c90c6c5771e7df230fb847e3a9426bf18a8f312": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "40aa0011039c7b0946bb1264199ba576db480b932c341fa5a224340056ec0976": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "79ccc13c690d354291ea1fbee6c0b103d273130dd90bb6f3efe51840d1c562ae": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00112233445566778899aabbccddeeff" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-integer-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-integer-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-integer-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-integer-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-1" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-3" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-21" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-integer-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_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." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json new file mode 100644 index 000000000..96b428ade --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json @@ -0,0 +1,1094 @@ +{ + "name": "obfuscation-numeric-boundaries-number", + "description": "Lossless numeric reads are independent of the numeric wire label.", + "context": { + "targetingKey": "fixture-subject", + "attributes": {} + }, + "response": { + "data": { + "attributes": { + "createdAt": "2026-01-01T00:00:00Z", + "flags": { + "b4ba8411e13132261ecb4f57a72e7ca330d2618c536f5546590abb0cc25b9c47": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "82796957263227e5d42ba913e864aca7aa0f6c06cf30ef1bd6a52cf771a5680e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9889dbdc0d0d8741c09f2d9e1f6b764e37995c66e76452502ff7a73398dc8fdb": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "20c38d5ef57bd5c41cc52d311ab1147afabfcdbb2abbdaf47f9c1d5f71736248": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a989158eea675eb49b1f62e15c2205b1a1168d9ed769e90e16ba8f43e65963c2": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "fd02ac0da0683586431856ba869c1ff07669b94244e6c8a58c7e1ae8dcb3a04d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483647, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "0c4bdf647950f33d16dace93f197e1891b73d767a5bc3be0cf1e51a115775f63": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "bb03a68efa88e918862e8a481ee25893c059765384e7a0835b022f742ecffc05": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "596ef4687c078263b87dd17061e7471add9255b49814d72150bac75dce35787d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -2147483649, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "920cb9612332743d61020ef481f4990bf0826040b3d087212a891115e9f30f22": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "62e88abd1cfc891df7f5703503077e82fb99db1e9be9b91d9a6b50333b4d1110": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "a99b58377ef7b9e52d6e1c3befd549d61e1ca234eca3cd8d4720c16928d9a7a0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 2147483648, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "ff66a9c09d22765cf9397d3e156bce434a683325f21ab6df1c9b7359056d676a": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9b15e9496c8eb9e19f2e8afc18ac298593ea8d45370e1fceb0f98b0f47e28fdb": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740992, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e0d4ba65f888bbf2b204e1ed4cc02d23b0e0c3f8a0f4f22b51501c7532d4781f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9b5089501d028a056aae6a5de88452f98f79340ba32f765a3950c6d8c0a82a9d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "1706e7b05d435e118cb4dee1540e2109fd7a7b1b19b08fceec0cfebc8fc0abc1": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "3769e087d36053e2971961b47640ab9b7397f02b1eddda5d4e90819ef1b1f5bb": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9007199254740993, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e0cff618c57a7aca3a66fbbb36b72aa53a4d4b7a08a5c67a0e25ffd564cea395": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4d204756440205698d05663c5a511d991d8e7ad64f54b1a257034b8e2c52f80f": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 9223372036854775807, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "4c741509a1ac866498f61619846c2cdec7fd84bbc362ee6ff3f8d563f2156fc8": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c9bbbec289c2c83e830e9bffd2ff577daf18bb71371cff7c08a5372ed860d957": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": -9223372036854775808, + "reason": "TARGETING_MATCH", + "doLog": true + } + }, + "obfuscated": true, + "obfuscation": { + "scheme": "flag-key-sha256-v1", + "salt": "00112233445566778899aabbccddeeff" + } + } + } + }, + "evaluations": [ + { + "flag": "numeric-boundaries-number-0", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-1", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-2", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-3", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483647, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-7", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-8", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -2147483649, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-9", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-10", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 2147483648, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-12", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-13", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 9007199254740992, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "numeric-boundaries-number-14", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-15", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-16", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9007199254740993, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-17", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-18", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 9223372036854775807, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-19", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -99.5, + "reason": "ERROR", + "errorCode": "TYPE_MISMATCH", + "variantKey": null + } + }, + { + "flag": "numeric-boundaries-number-20", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "flag": "numeric-boundaries-number-21", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -9223372036854775808, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + } + ], + "expectations": { + "noUnmatchedEvents": true, + "exposures": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-0" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-1" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-2" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-3" + }, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-4" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-5" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-7" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-8" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-10" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-11" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-12" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-13" + } + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-14" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-16" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-18" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-20" + }, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-21" + } + } + ], + "evaluationEvents": [ + { + "_count": 1, + "flag": { + "key": "numeric-boundaries-number-0" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ] + }, + { + "_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." + } + ] +} diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json index 6547f39aa..fdbedade4 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-float", - "description": "numeric float.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,119 +10,125 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "4fc70afd0edbe9f6c3605f50b73892f4167f395a93aa5c2e91c5380818270237": { + "43dcecc88807de972a354bd0b96946419ccf086a3cb1abcaef10a796129ffb7e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "01c7520392b183ba08426484c8981ef7b9574d8d5e1f0b5b0afc04945b1fdc65": { + "ad5896fa25ecdb06cc985d99e469f2b867419c296d99142ba53b7a4ab11d0167": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "30a0b6a28e18bbdd712d677df8b0a0012086219260528d4967214fa45f36f90b": { + "5f920ae1126d6bf5dd3c7cde59b1604dfbae53d4ad4686a548871a4baec00f22": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "b432d3d8d83d0f10acfb8d8099c7dd1238b96b45e12f2407ae6bfd938ad92354": { + "a710178c1d9213803a5ff5f56146c93908327ac97407d76c6139dfdcd9a4012f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "26a957788e3e02fda8ace878f452615d85c2d80a539a19522b45c9fac903b905": { + "b81af3ac1aba6361e1eb0951f2e1babd617d2218827ce4a14f2c8a5f8005b885": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "fd7b22e26bc6227562127dc3945e30e159e1b522ba003b52706d5b33c8db0519": { + "de8b6921dbff87fdf7182b8a948b9644aa4db01a9dbbda9749a0774f189746cc": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "93cafc55f2224734ccd9a3a0030a8bd007dcded0f48320a16d2044744cc6a4f6": { + "9a8d577fc6f24f49f804fa068b9a7170c9328c0def19a2b2312c9f59b5dfab02": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "5ad25a909e7b8ef40cf13c71a7ec8f2ab4bb8a3ae4a5439029357f9982508bf8": { + "82077f26295c6ead94d7a7a7554ed4e143a4af711f8deb94b8a8a711d032c4f2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "4d677ea458fe5ed9851dbdba4f92981a432b5b9b467445032483b9fa2d2f7368": { + "8bf2c56f7653627e757f8263815d7408dbcbb6fb86d71d55402e9800f185ed22": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "eb98f328fb6978a74722d54badcaf0335bc7433ca86e205ecca0be0ac86012c8": { + "cef1691308f6e520bbdbc8575683d17bc814f52af8ef42522d6a5bf0aee7e861": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "8b79dc5672bf5c07c2a64b68cd885ba18db98b52639d349283ac6888477326a6": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c0b84d2e8690addb339fd433c9cc449d6da6632ea8398d47e9bfebde442247a0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "float", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } }, "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "000102030405060708090a0b0c0d0e0f" + "salt": "00112233445566778899aabbccddeeff" } } } }, "evaluations": [ { - "flag": "float-0-as-integer", + "flag": "obfuscation-numeric-float-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-0-as-float", + "flag": "obfuscation-numeric-float-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -133,28 +139,18 @@ } }, { - "flag": "float-5-as-integer", + "flag": "obfuscation-numeric-float-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { - "value": -99, - "reason": "ERROR", - "errorCode": "TYPE_MISMATCH", - "variantKey": null - }, - "_skip": [ - { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - }, - { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." - } - ] + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } }, { - "flag": "float-5-as-float", + "flag": "obfuscation-numeric-float-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -165,7 +161,29 @@ } }, { - "flag": "float--5-as-integer", + "flag": "obfuscation-numeric-float-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-float-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-float-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -174,30 +192,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--5-as-float", + "flag": "obfuscation-numeric-float-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float-1.25-as-integer", + "flag": "obfuscation-numeric-float-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -206,30 +232,38 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float-1.25-as-float", + "flag": "obfuscation-numeric-float-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": 1.25, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "float--1.25-as-integer", + "flag": "obfuscation-numeric-float-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -238,23 +272,31 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "float--1.25-as-float", + "flag": "obfuscation-numeric-float-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -263,48 +305,218 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], + "exposures": [ + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-0" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-1" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-2" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-3" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-4" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-5" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-7" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-9" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-11" + } + } + ], "evaluationEvents": [ { - "_count": 10, - "_skip": [ + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-0" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-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": "obfuscation-numeric-float-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-8" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "Single Number API." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true }, { - "_count": 5, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-float-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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": "obfuscation-numeric-float-11" + }, + "evaluation_count": 1 } ] }, "_include": [ { "platform": "ios", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "android", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "flutter", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json index 7380abfe8..4b638c491 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-integer", - "description": "numeric integer.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,66 +10,114 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "3a56e171b0efb081585be54227ac1792ebae79900fb0e0ee2c3850ebc7dd7fd0": { + "72101f81c2d474893bb90b2e9fbece5750b83659171b75d423eba3c8caa09af8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "3df507231bc602ed78f6e01db4497a88b33c086a455deba043f35dde999c0b7d": { + "158853f6fec44bdd14c7dcf732538d1932393333da0d38e5b1cf389febab173a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "58e7c159ddb85fe9250395e7797d80ecb7dca88e1a22767261a9482611baa358": { + "5e716e405677ef15a943d1e3ec8577a5436ad2b08b619801fa115e3f6056e7e1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "a4ef458efa0a3876a8509351d70fa007a68460b21c64cc64680c1099ec450dab": { + "55aa24315699bb2e6b22e1e60dbc59588628d4987393d1709e63283f740da782": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "01c7f586a7f7bd98d4cbc75760f506b6ff8a9ba7195e88f975a708cc7b6ebb30": { + "11dbbd2cc435f0d8521f492798f5cdeda39b44612afb5b2e8dbbca244eac8afb": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "14a559f9d240120a5038b7f3f6529a0cba11d3cbc84409c84678290f4116b809": { + "5f600bc0a426abe99c1d0f650b9b8eb243179105c4a73a59f696b99967088fcd": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "422fd4f38b4009a3443ac2334e4687d21e2eb02d7614a3eec83c7630585d7f4e": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "77e8ec1779ffe7a8edd77b8c84358cae99993a9898b4c0cadf2336a88ae99ccd": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.25, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "6779f3038ba17b00bc22bd42f0fb7078c7a3cf7843adc7884e269ad0b33a0180": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "7224ebe3cb4e571ec923f2624b68bd3e183c199258e5523e6456b62e9254f8e5": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": -0.5, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "9483e1046878fedc712a9345a3e8406df5b24d42f7e0d4500ff8cc9fb1c9a161": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "e3e9c957b3953d38da8ada7fadcb6bea2ce78691d7cc34dbc2c75297ee4c5a95": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "integer", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } }, "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "000102030405060708090a0b0c0d0e0f" + "salt": "00112233445566778899aabbccddeeff" } } } }, "evaluations": [ { - "flag": "integer-0-as-integer", + "flag": "obfuscation-numeric-integer-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -80,135 +128,395 @@ } }, { - "flag": "integer-0-as-float", + "flag": "obfuscation-numeric-integer-1", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 0, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-2", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -99.5, + "value": 5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-4", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-5", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-integer-6", + "variationType": "integer", + "defaultValue": -99, + "expectedResult": { + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer-5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "obfuscation-numeric-integer-7", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": 5, + "value": 1.25, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer-5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "obfuscation-numeric-integer-8", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "integer--5-as-integer", - "variationType": "integer", - "defaultValue": -99, + "flag": "obfuscation-numeric-integer-9", + "variationType": "float", + "defaultValue": -99.5, "expectedResult": { - "value": -5, + "value": -0.5, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" } }, { - "flag": "integer--5-as-float", - "variationType": "float", - "defaultValue": -99.5, + "flag": "obfuscation-numeric-integer-10", + "variationType": "integer", + "defaultValue": -99, "expectedResult": { - "value": -99.5, + "value": -99, "reason": "ERROR", "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] + }, + { + "flag": "obfuscation-numeric-integer-11", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": 1.2, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } } ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], + "exposures": [ + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-0" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-1" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-2" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-3" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-4" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-5" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-7" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-9" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-11" + } + } + ], "evaluationEvents": [ { - "_count": 6, - "_skip": [ + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-0" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-6" + }, + "evaluation_count": 1, + "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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": 3, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-8" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." + } + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-integer-11" + }, + "evaluation_count": 1 } ] }, "_include": [ { "platform": "ios", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "android", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "flutter", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json index 9e54ced94..925306497 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-number", - "description": "numeric number.", + "description": "Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,98 +10,114 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "085fa5b25a3d42952668a59d052deb6ee5bc2418a352b9887556fe862ef78ca8": { + "5d1d3c167fda7f3ecadb68da40228431ed0b74ec08597fc57398fbf7f253f0d6": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "66aeb85f02e96149bd485b28375161c4bc154e748545593a40da549d53f479c2": { + "4dcb0659aaeb33eb329143c5ed994c52800521968edafd58c95c50377873ccc4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 0, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "eef7048e9d655ec6f29019eec43986e1d4ba6ff42523ecd8f46b6ac0ae883050": { + "8023572892cfed9fb5ee411c451eab7480329f32331f3845e76234275bede04d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "a46a4fdb29e1de92315dc1ee481f59a33455841d317e38191f13cdcdd9fd2eb0": { + "d15970ae43ef67b0a9844f04cd8680567400e12340d545aa608a0cc9b89bed95": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "5f25ccca362e35bf27475aa056da166b4e7ecf6ed66b65bc25f9d573717d2628": { + "4373eda204ffa94cc5c15d29bd5a33b828033f4d2382f708801a7ea5c298b286": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "914ae5836aae14f5bd8348d1669e008e400c43887d08cd6dc67b028d914e8c6a": { + "5a95ab1a979318a8d113fcdb44a9a5262f0f99a6fb58e0f782b1ac5dbc88f1ef": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": -5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "3cecd82b1b8e3c49bc3fe1886fdd0a57de07ac68a437f59bf7c6c6317d83b03b": { + "dceef8e665186596db59e1c8bf2887adf20969530b48091e1bcf3517bf804a7b": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "5a539f4ac96a34becffeed98230a919b3082e085bb79deed40d88600d5500636": { + "6b8f85c278e445167be847d3a94a91c1865221df7cc63dc8f31aacbb9454e430": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", "variationValue": 1.25, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "ec97872ee38dc677992d1df24af2093298d35a35d246ea3dd39df7e33279b358": { + "81da1069b7cb802d039c7cd27d4c0f4db2515a5c64dd411a522d6f300592c31c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true }, - "4b33526589906dcabeefa0e880ad6697d4e6673af26187c67947124777a67bbc": { + "0a2bcee9cec623b02ca219d7b3f15415767bb630f6bcf9af75fda36c2a689f64": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": -1.25, + "variationValue": -0.5, "reason": "TARGETING_MATCH", - "doLog": false + "doLog": true + }, + "be4ecda1ccdb165dc68fcffc30f021c7e83a88b8a2283784498ac761385f26d0": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true + }, + "c4f3ba7ee684fddd93335901ae9b0af3959704d06b5a028449b08854d5384e3d": { + "allocationKey": "allocation", + "variationKey": "variant", + "variationType": "number", + "variationValue": 1.2, + "reason": "TARGETING_MATCH", + "doLog": true } }, "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "000102030405060708090a0b0c0d0e0f" + "salt": "00112233445566778899aabbccddeeff" } } } }, "evaluations": [ { - "flag": "number-0-as-integer", + "flag": "obfuscation-numeric-number-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -112,7 +128,7 @@ } }, { - "flag": "number-0-as-float", + "flag": "obfuscation-numeric-number-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -123,7 +139,7 @@ } }, { - "flag": "number-5-as-integer", + "flag": "obfuscation-numeric-number-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -134,7 +150,7 @@ } }, { - "flag": "number-5-as-float", + "flag": "obfuscation-numeric-number-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -145,7 +161,7 @@ } }, { - "flag": "number--5-as-integer", + "flag": "obfuscation-numeric-number-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -156,7 +172,7 @@ } }, { - "flag": "number--5-as-float", + "flag": "obfuscation-numeric-number-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -167,7 +183,7 @@ } }, { - "flag": "number-1.25-as-integer", + "flag": "obfuscation-numeric-number-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -176,19 +192,27 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number-1.25-as-float", + "flag": "obfuscation-numeric-number-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -199,7 +223,47 @@ } }, { - "flag": "number--1.25-as-integer", + "flag": "obfuscation-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": "obfuscation-numeric-number-9", + "variationType": "float", + "defaultValue": -99.5, + "expectedResult": { + "value": -0.5, + "reason": "TARGETING_MATCH", + "errorCode": null, + "variantKey": "variant" + } + }, + { + "flag": "obfuscation-numeric-number-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -208,23 +272,31 @@ "errorCode": "TYPE_MISMATCH", "variantKey": null }, - "_skip": [ + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, { - "platform": "web", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "The public API exposes one Number getter, not distinct Integer and Double getters." + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } ] }, { - "flag": "number--1.25-as-float", + "flag": "obfuscation-numeric-number-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { - "value": -1.25, + "value": 1.2, "reason": "TARGETING_MATCH", "errorCode": null, "variantKey": "variant" @@ -233,48 +305,218 @@ ], "expectations": { "noUnmatchedEvents": true, - "exposures": [], + "exposures": [ + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-0" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-1" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-2" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-3" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-4" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-5" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-7" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-9" + } + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-11" + } + } + ], "evaluationEvents": [ { - "_count": 10, - "_skip": [ + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-0" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-1" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-2" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-3" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-4" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-5" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-6" + }, + "evaluation_count": 1, + "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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": "obfuscation-numeric-number-7" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-8" + }, + "evaluation_count": 1, + "_include": [ + { + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "android", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "flutter", + "reason": "This case requires the stated native numeric accessor range." + }, + { + "platform": "unity", + "reason": "This case requires the stated native numeric accessor range." } - ] + ], + "runtime_default_used": true }, { - "_count": 8, + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-9" + }, + "evaluation_count": 1 + }, + { + "_count": 1, + "flag": { + "key": "obfuscation-numeric-number-10" + }, + "evaluation_count": 1, "_include": [ { - "platform": "web", - "reason": "Single Number API." + "platform": "ios", + "reason": "This case requires the stated native numeric accessor range." }, { - "platform": "react-native", - "reason": "Single Number API." + "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": "obfuscation-numeric-number-11" + }, + "evaluation_count": 1 } ] }, "_include": [ { "platform": "ios", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "android", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." }, { "platform": "flutter", - "reason": "Implements assignment-encoding-flag-key-256-v1." + "reason": "Requires native numeric precision or supported obfuscation." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/index.json b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json index dd4179273..d3d7c926e 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/index.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json @@ -9,6 +9,9 @@ "cases/evaluator/integer-precision-native-only.json", "cases/evaluator/multi-flag-set.json", "cases/evaluator/multiple-reasons.json", + "cases/evaluator/numeric-boundaries-float.json", + "cases/evaluator/numeric-boundaries-integer.json", + "cases/evaluator/numeric-boundaries-number.json", "cases/evaluator/numeric-float.json", "cases/evaluator/numeric-integer.json", "cases/evaluator/numeric-number.json", @@ -16,6 +19,9 @@ "cases/evaluator/special-characters-in-keys.json", "cases/evaluator/type-mismatch-all-types.json", "cases/obfuscation/obfuscation-keys.json", + "cases/obfuscation/obfuscation-numeric-boundaries-float.json", + "cases/obfuscation/obfuscation-numeric-boundaries-integer.json", + "cases/obfuscation/obfuscation-numeric-boundaries-number.json", "cases/obfuscation/obfuscation-numeric-float.json", "cases/obfuscation/obfuscation-numeric-integer.json", "cases/obfuscation/obfuscation-numeric-number.json", diff --git a/packages/core/src/flags/configuration/__tests__/precomputed.test.ts b/packages/core/src/flags/configuration/__tests__/precomputed.test.ts index ac54e1259..a1e81892a 100644 --- a/packages/core/src/flags/configuration/__tests__/precomputed.test.ts +++ b/packages/core/src/flags/configuration/__tests__/precomputed.test.ts @@ -219,15 +219,14 @@ describe('decodePrecomputedFlags', () => { expect(decodePrecomputedFlags(responseWith({})).size).toBe(0); }); - it('omits an integer flag with a fractional value', () => { + it('preserves a numeric value independently of its integer label', () => { const cache = decodePrecomputedFlags( responseWith({ frac: flag({ variationType: 'integer', variationValue: 7.9 }) }) ); - expect(cache.get('frac')).toBeUndefined(); - expect(InternalLog.log).toHaveBeenCalled(); + expect(cache.get('frac')?.value).toBe(7.9); }); it('omits a number flag whose value is not finite', () => { diff --git a/packages/core/src/flags/configuration/precomputed.ts b/packages/core/src/flags/configuration/precomputed.ts index 69065464e..e241b934b 100644 --- a/packages/core/src/flags/configuration/precomputed.ts +++ b/packages/core/src/flags/configuration/precomputed.ts @@ -187,13 +187,10 @@ const valueMatchesVariationType = ( case 'string': return typeof value === 'string'; case 'number': + case 'integer': case 'float': // Reject NaN/Infinity: native parsers can't round-trip them. return typeof value === 'number' && Number.isFinite(value); - case 'integer': - // A fractional value under an integer flag would be truncated/mis-parsed - // natively, so require a whole number. - return Number.isInteger(value); case 'object': // The `object` variation type carries JSON. ffe-service enforces a top-level object at // the API layer, so in practice the value is an object; that is not a storage From 2a7858bd53f09e01c1d4d11888e9049544d4a76b Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 16:25:32 -0500 Subject: [PATCH 5/8] FFL-3568: Pin checkout and satisfy fixture importer lint --- .github/workflows/precomputed-fixtures.yml | 4 ++-- ci/sync-precomputed-fixtures.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 ci/sync-precomputed-fixtures.py diff --git a/.github/workflows/precomputed-fixtures.yml b/.github/workflows/precomputed-fixtures.yml index 8996657e7..779af055b 100644 --- a/.github/workflows/precomputed-fixtures.yml +++ b/.github/workflows/precomputed-fixtures.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - name: Verify the local snapshot @@ -38,7 +38,7 @@ jobs: with: scope: DataDog/dd-sdk-reactnative policy: self.precomputed-fixtures - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - name: Import upstream fixtures diff --git a/ci/sync-precomputed-fixtures.py b/ci/sync-precomputed-fixtures.py old mode 100644 new mode 100755 index d47e01c1f..e6ecb047e --- a/ci/sync-precomputed-fixtures.py +++ b/ci/sync-precomputed-fixtures.py @@ -7,11 +7,11 @@ import hashlib import json import os -from pathlib import Path import re import subprocess import tempfile import uuid +from pathlib import Path REPOSITORY = 'https://github.com/DataDog/ffe-system-test-data.git' PREFIX = 'precomputed-assignments/' @@ -56,7 +56,7 @@ def snapshot(source, ref): if not isinstance(data, dict) or data.get('name') != Path(path).stem: raise ValueError('Invalid fixture identity: ' + path) if not isinstance(data.get('evaluations'), list): - raise ValueError('Missing evaluations: ' + path) + raise TypeError('Missing evaluations: ' + path) result['index.json'] = (json.dumps(cases, indent=2) + '\n').encode() result['SOURCE.json'] = (json.dumps({ 'repository': REPOSITORY, From 508b422519409c1ccb5ef6bf85ac8e4a31fbc7fa Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 18:29:32 -0500 Subject: [PATCH 6/8] FFL-3568: Sync generated obfuscation coverage --- packages/core/README.md | 2 + .../__tests__/fixtures/precomputed/README.md | 34 ++ .../fixtures/precomputed/SOURCE.json | 31 +- .../obfuscation-all-doLog-false.json | 106 ++++ .../obfuscation-duplicate-evaluation.json | 82 +++ .../obfuscation-serial-id-on-exposure.json | 142 +++++ .../obfuscation-all-types-success.json | 161 ++++++ ...fuscation-defaults-and-emission-gates.json | 130 +++++ .../obfuscation-empty-flags-response.json | 83 +++ .../evaluator/obfuscation-falsy-values.json | 148 ++++++ ...scation-integer-precision-native-only.json | 66 +++ .../evaluator/obfuscation-multi-flag-set.json | 490 ++++++++++++++++++ .../obfuscation-multiple-reasons.json | 110 ++++ .../obfuscation-numeric-boundaries-float.json | 54 +- ...bfuscation-numeric-boundaries-integer.json | 54 +- ...obfuscation-numeric-boundaries-number.json | 54 +- .../evaluator}/obfuscation-numeric-float.json | 100 ++-- .../obfuscation-numeric-integer.json | 100 ++-- .../obfuscation-numeric-number.json | 100 ++-- .../obfuscation-object-nested-deep.json | 106 ++++ ...bfuscation-special-characters-in-keys.json | 127 +++++ .../obfuscation-type-mismatch-all-types.json | 148 ++++++ .../__tests__/fixtures/precomputed/index.json | 25 +- 23 files changed, 2207 insertions(+), 246 deletions(-) create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json rename packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-boundaries-float.json (93%) rename packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-boundaries-integer.json (93%) rename packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-boundaries-number.json (93%) rename packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-float.json (80%) rename packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-integer.json (79%) rename packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/{ => generated/evaluator}/obfuscation-numeric-number.json (79%) create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json create mode 100644 packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json diff --git a/packages/core/README.md b/packages/core/README.md index d8f3f52ae..6ddd72684 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -147,6 +147,8 @@ Fractional Integer reads, range overflow, and inexact conversions return `TYPE_M React Native exposes one Number getter. Its tests exclude cases that require a separate Integer getter or native integer precision. The service keeps sending `number`. This work adds no numeric capability. +Upstream CI requires a generated obfuscated companion for each applicable plain fixture. It rejects missing, stale, or orphaned companions. Edit the plain case upstream and regenerate; do not maintain two sets of expectations. Dedicated malformed-encoding cases and fixed digest vectors remain separate. + Cases cover values, defaults, errors, reasons, variants, emission gates, duplicate evaluations, and numeric boundaries. Obfuscation cases cover fixed digests, salts, Unicode, whitespace, malformed metadata, and plaintext event identity. React Native has one Number getter. Cross-type Integer/Double cases are excluded by the shared contract. The suite checks the real decoder, public getters, and native tracking arguments. It does not validate native event serialization or delivery. Encoded responses are rejected because this decoder does not support key obfuscation. diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/README.md b/packages/core/src/flags/__tests__/fixtures/precomputed/README.md index cf76ab45c..380812a7f 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/README.md +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/README.md @@ -213,6 +213,40 @@ emit one event with `evaluation_count: 2`. Exposure deduplication is independent Runners must inspect real event records, not synthesize them from expectations. Keep native bridge and deployed-backend proof separate from mocked conformance. +## Plain and obfuscated coverage + +Every plain response fixture has a generated obfuscated companion. This checks +that encoding preserves public evaluation results and emitted events. Dedicated +malformed-encoding cases, unsupported-consumer cases, and fixed hash vectors +remain separate. They do not require plain counterparts. + +Edit the plain fixture, then regenerate its companions: + +```bash +python3 ci/precomputed_obfuscation.py --write +python3 ci/precomputed_obfuscation.py --check +python3 ci/validate-precomputed-fixtures.py +``` + +The generator discovers plain JSON files recursively. It changes response-map +keys and encoding metadata. Public lookup keys, values, results, and event +expectations stay unchanged. The generated platform filter intersects the +source filter with consumers that support obfuscation. Evaluation and event +filters remain unchanged. + +Generated files are under `cases/obfuscation/generated/`, preserving source +categories and nested paths. Do not edit these files directly. CI compares them +with freshly generated content and rejects missing, stale, or orphaned files. +Adding a normal case needs no generator list or SDK runner change. + +When encoding does not apply, add the source path and a reason to +`obfuscation-exceptions.json`. CI rejects blank reasons and stale paths. An SDK +failure is not a reason for an exception. All current plain fixtures have +companions; the exception map is empty. + +The independent digest vectors remain fixed. Generator regression tests check +those known digests, including Unicode, whitespace, and embedded NUL keys. + ## Snapshot maintenance Consumers can copy `ci/sync-precomputed-fixtures.py` and configure the destination diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json index eae45353c..13c31d7d3 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json @@ -1,10 +1,10 @@ { "repository": "https://github.com/DataDog/ffe-system-test-data.git", - "commit": "0feda21aec6710a5243287fbc58a33ca2f658b16", + "commit": "8dcc09a53dfb654cb0b2de8cda96919710f074e6", "files": { "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", "NOTICE": "a5245f10dbda39cc65bad11ab3a6022b9e5c05b259da3f7aa0302d932f31083e", - "README.md": "9d2d59f1d66752d0c0e3828217fc7593a6ffed6992e7cf6e417a61ca336b8fd1", + "README.md": "ceb34cf7027004641182ec3327963adc1bd7b8a2b5f6faed94cd67f4a548d956", "cases/emissions/all-doLog-false.json": "1902637564cde9a7105a80730bfbeb9ce57178a59910c905fad03d7a1118b4de", "cases/emissions/duplicate-evaluation.json": "88c1dbdcf3c1bb1ee8dd9a0842123447921b9d9fe806f26d24cc41a7d73bb0cc", "cases/emissions/serial-id-on-exposure.json": "83a7504e6658b875ececdd1972c04386130feb31c607ccf7b61cd45ce46c1fc0", @@ -24,13 +24,26 @@ "cases/evaluator/object-nested-deep.json": "22f8f0c516cfe0456d34697f29bbf39d7736559e0b188de4671d0fee6545fd3d", "cases/evaluator/special-characters-in-keys.json": "07e24ba1a407ef9604af0bc5ab3f8faafe199dd83db94d96d0e3d689430054ce", "cases/evaluator/type-mismatch-all-types.json": "0904425126ae64794a8c67d13d4bab759a88abc75c27c99f71c514babb9279a3", + "cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json": "daa6440ae7975580da89b38d71d9c60228858c022df3cd53ba0d906ef335881e", + "cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json": "8a98ce923ea0f56b6c1473a70c2cef2cd7b1892425b068a8e16ac127f3fe6089", + "cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json": "c7d24f386159b73bca86a2c10a58c0afc0fe6b17a557739d5b7f051c3a6aa205", + "cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json": "57bdf315572c3be3e2d997bc516a1332746a465c0e0a370d25f253947075b584", + "cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json": "8eff282ef31291bd85fe33d373711dc470adb450e11eac10da8c465f52a317b8", + "cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json": "cbf05f9e2d24d028a8fe9fceb5ad0d4d3e3162fdfdf51958662a7ef0943f4ce3", + "cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json": "54f2db7cc521fe33c21de4acb84d6dcbb6b625f2634c6fd7ccb7ec82121c8ff6", + "cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json": "f8f7470298fb30ee4566292ca9eb96c543b7009fec513199f5e676ab910efb88", + "cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json": "ffb46863e42fb3161e4c46c4c7c30f556c69c4c3f6172ff98541f3e37e34f1f7", + "cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json": "2955d89877e2a45a7301a7c00cadc2f7826b2e1fea083941a6928f88c171f48d", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json": "3ad1a96dffe7d376e8c381b6b089fb7cd30423b39d8f7f4006fa86fdab3a78bd", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json": "01269d0992978348fe0cba509978ba4b10e65a7bb03284847efb1be1a014d7da", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json": "df03044f0726dfe353b172ae75daf205fa4280a76ec05ac4dfc310f51ca186f6", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json": "702674897e5c457c070e0abdb112a38e5c1274d7e21ae314f5e2dfa805320a41", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json": "13a177de29ae708fd8361c095279d78166ae82d2fbe8d25804d5790d527bd3d1", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json": "3e8a78b7016acbcce301e1f437f145f9a24737989d70bda00fba2f9e8ffb2f11", + "cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json": "ff2ba07eb266db0669f0f86926060d268f3242d437264b5d4663f259317d6bc1", + "cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json": "fd4f9efb1eb115e505c81a30ae82c51766747cbc26ae5a36dda961a4cfeb2cd8", + "cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json": "6dc2320ceb7013825dbda712ed789689a0bd3b36294a741a8bc23371bbe7db91", "cases/obfuscation/obfuscation-keys.json": "3af487d59d5a7064e0e8e6508343db21689180d7d6e862b055934036e1716643", - "cases/obfuscation/obfuscation-numeric-boundaries-float.json": "56b4343a717161047ce32138f564c2558a1da49f37f26b7681f94b3cc0ef5473", - "cases/obfuscation/obfuscation-numeric-boundaries-integer.json": "3f2340a46e9cdecd48d07cc2aacdf66cba66fb192ed300e42dda4bbf50e415d7", - "cases/obfuscation/obfuscation-numeric-boundaries-number.json": "bca42ecb5cbb3a6ba08a6adbfa34690accaf986a07bf30a91b764871db673566", - "cases/obfuscation/obfuscation-numeric-float.json": "57ec0a367eb86a94269e353b7ceb28a83ac96a546a779b36efa115fd50464c69", - "cases/obfuscation/obfuscation-numeric-integer.json": "f3954848aa7776f4aed3d05e23445f0a66d5c5aaa461e526db335d7034dbc015", - "cases/obfuscation/obfuscation-numeric-number.json": "c429b7ed0a5e1131b25b1240739c1b5a17b046f833f7d54d74f60d90642c0c2d", "cases/obfuscation/obfuscation-reject-false-with-descriptor.json": "452e57fbcec5a254c65295014ae11675d406a87567e6887933ba493a14109093", "cases/obfuscation/obfuscation-reject-missing-descriptor.json": "f0105da777c3b35ff10c7082e62ddd1f7ce6a4bc15dae27302f279b18b3bfc29", "cases/obfuscation/obfuscation-reject-missing-marker.json": "4fb270faacdcfe92477120f9bf9ea56f98e1503cf992c72da71ecc6fbca5e0dc", @@ -49,7 +62,7 @@ "cases/obfuscation/obfuscation-reject-uppercase-salt.json": "65f4a35f6042863cbec5f19634fa9526da1e6c66ddecf3aad488f4455e26ec7e", "cases/obfuscation/obfuscation-rotated-salt.json": "a97bf1f0d57ad66d122a4286433ba216469d8de2824e03b2d79fd860b87e277d", "cases/obfuscation/obfuscation-unsupported-consumer.json": "d632485351b4b6cb7b550769cde55c46960ebee7ac811059a5147f143524554f", - "index.json": "e3fc034a98f756a4283e1a10251800fa30d20deac2268c0193a3682ceeb679e6", + "index.json": "de2340499732d87bb4eda3fc0c0e3e2f188bda6f105bcf659c5d8824e9ca9d48", "schema.json": "0353d393b1b4193346b2fa907f500b5c60be51318759c3afe52e549f1662195d" } } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json new file mode 100644 index 000000000..a8fb1e7fa --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json new file mode 100644 index 000000000..c5a6e355d --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json new file mode 100644 index 000000000..4c038b049 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json new file mode 100644 index 000000000..8b20e3f03 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json new file mode 100644 index 000000000..b9a7541d6 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json new file mode 100644 index 000000000..c68516798 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json new file mode 100644 index 000000000..e6bd5b9e4 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json new file mode 100644 index 000000000..8ab272bb1 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json new file mode 100644 index 000000000..0883414b5 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json new file mode 100644 index 000000000..4002b7247 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json similarity index 93% rename from packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json rename to packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json index 4ad1d6942..90483ddba 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-float.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-boundaries-float", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "2aec5b3d4477769702ec52c42349be07a31c413ebffb69196b762523adf205ab": { + "35dd7544a6b9a875c5074a893aa3d5c95f015b6cff7f07f30390bc90b939839e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ccd840e29b0846bbc8ace7da7bc20c300b2a14e1f5f68961f2fb76e69fb0ba04": { + "54b3e4e77e354d4e049991350fe68ce1a90ba4e874933a9e269d5b99cb3ad115": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ebe608c5e7ab0bf15e0c6585810759616ab5881130d7ddb956ec8749c43a0939": { + "6cec42afe677ad01472e4754a611106037ddb8e786570bf9a730630b8d2f7f30": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "1f2de3a9ac37932ca8fbeaafe88046f8f7a49075c87eec5b805a0967706685a4": { + "8de4314e7ce11a9f64a3490e59968a1130f56b15c39276bdff9ebc344402101d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "431adeb8d8d06d55e943812fd5cf8bf6095858eb898fd3133999c3d163607b3d": { + "4182dc078ece15bc1c45cabf6e2ab2ad0b7acaf7f42eaa3ad63fe3956beb1db7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "2c7494350d45235245c36589fe36990e14f5e3eca4522e7c93b05ec4e4e3de7c": { + "ec7fd610e215fd5d177ffa68bec26d50a871bc1cf0114636a734e1660bd6411e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "19b8019e6dc73fdabe4884fd109c0c0865eb682b5a6e9b2c098d8698cb8a25bf": { + "53d34567f5337f1104d86a9e821df4511fa74a9cc03516f426c9ab4c7af0ac52": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "05dce5b4f6ec9f670d767a9e2f3095c91dc5f947c0cf65f26ffe0d34d169c27a": { + "3e69e701cc02087f7897b6f4832e8be0be9ca155876f8cb95597e6ad32b1e2ab": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "b31d785097e2b21ff4203b7f42d9601f0fe117e04ae52fd41c38ec226ee7c9dd": { + "bd1a3e85e06626dbb1c56432a8dea7a0128284f1d781013835a2564e70f87d98": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "2561c2c5e59e5ee958e65c5ffc5afdc82deab8caacc24b7fd955426d38f3f10a": { + "a212f6d288564e940e009835514eaad55a3d62caada53dd1fdda1b2475efed0a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a4378fa74480657ce942442b4755051dfe8254b3a869ef1d09e73b70905931ba": { + "f8dc9d43242ec89a4db2212c3a70ee9c938b0cb36afac45b2000cd7c77bf7609": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4c973da42e257593047a0e547423acae08bcb5d6529670d6917dc653d559878d": { + "b1e20092aa2b67b3e8ed9d5533a2260ff177207d9b86fac2151864b2d7af492e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -106,7 +106,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6291d2c93c31947490a82ac572b34ff0a2cfc6d1bf63ee929bd4c427e4c14e46": { + "45f0d04656e98eb32b8221714cf169d57479d2be4af6b96c5ffe0f9fd589ad0a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -114,7 +114,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ed9c529fd1165fc1f8560f540dd0741dd240473987473db250c027486ca54481": { + "680bb5138700a197f7896709b20d6a451854dfaf7fd2016374154dc214101f96": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -122,7 +122,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "87217664c8cdba2054ae7ba5e0513f1fe0932a93deb52ee46c8c4856db50bfaf": { + "5f0e609737393c76014f125cfe1d8b685065b3f8bb2833e393f4de4bb3f271fd": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -130,7 +130,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "435af2981e1d1cf3e2e762b9fbb1b99f4850d1b0d4bed9de0f44f0418b223506": { + "6de38d27b71396e8169bdf012bf53a9f6112b4f9533e492d1d0c372aa5b2b89a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -138,7 +138,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "608602ea98e8c47ffb9f86e51e8093daa81ff902841d774092b32df7f92421e2": { + "e68a8c78bf6d545e6c949e88209611b6615ddeea9817440de9fd3fb7a289c335": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -146,7 +146,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ae43ae724ec81be659b9cbd89315102b127b1b9e24e26def87496eb4a8aa38ab": { + "a6d6386fa18188d57ad46d118b937d77a1a8d814e3ff723871452b9d5f6abd03": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -154,7 +154,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "2358b34f81306a3acd892893294abea0c33721a65b06e355cda36c8e54739be5": { + "9d28b9ca14aed8cd8edab7144dc09ff50ff608f1676df0c92be1a225fc4c95b4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -162,7 +162,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9fc154f59fba732da1a2be979ec48044cd88f185fd90b9cfc306e53f3f6d6a88": { + "e026139b2c1ef067fcd194745a316b948ca17d4224a2c6ef0f54e42739a6f9e5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -170,7 +170,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "350edcb8f9921229dea893c8a3948457281d0ef88537c3c8905a4dd924165228": { + "9ad722f4aa0e6cbe77da0e6406a3f40ac0dbe3f556a524d1d504115896235fed": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -178,7 +178,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "3ff68a68d0c6c04348089c33052aad256a51086ae365496d7ecf89a55c9a4d70": { + "56cd8c449d33e0a19ca9b0d300b40bd994dfd1f4f3b16d093e453d8a97a3645f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -190,7 +190,7 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } @@ -1080,15 +1080,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json similarity index 93% rename from packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json rename to packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json index 9f6a4ef37..4acc8be2a 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-integer.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-boundaries-integer", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "7c5c55bbc1d947ebd29d4d22bbb8e854fb84a3168ec16c9b88ff1eb8048f1259": { + "dba4f4d05c42d94f2f0fe7fb4c5622432cbace9983c4f2bcd81dba39a6327250": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "1db93bef085224805fc0b94e254d8efea2ad144e255512c7822ba1941efd37ca": { + "c053d531267e94db3094b514ac132333832cf45cc102899e23a317cfd1b86c7c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c65f42d21a69e9e63e768bb7c353ccc659d8fa7fda19d552c46571fe747939a1": { + "cc6b41d8abf103fc2c0903beb89ea4916203e65e34f56f229bd4bfd30fca2b44": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "57ea520510c0e0ffdf6c016032a1ad8fd7e930feddb56e512c5ba5e176bb747e": { + "2cd758e8f6b3d6069426eaf4fe83cad350062d6f3cf9fdff78af6814151ff35f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "3bae488ac7f7c5e64e8d80c5e5d3a01fede98ee9dc1dbfaaa1602bdb834e3199": { + "be1e08347c503a3116b8872a98f762ce80f8b2d5d9712855f553e9a8e41a0e24": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "d7e31a5af23eaed253fbab5d5ba775a49cc869265a762861623b9850ca45200e": { + "439b8c652a5b3d6bb6a8b5601f86c09f38f1ca344dfa0cd10904c41d3a0089ba": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e214fbcdc5fa2e6b53e80a737cf3b01bf720e1da6bdc8d1a8126c82fae131cc5": { + "52c0be60636eb11b987e1729b894a63f02463a408660d632004cd68b66c17966": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "f87c7ffcbfc101c063bc2f6533d4a73054a4c3e0cc59b865b6d9a738cded6776": { + "32ac57434b06ca630219caa81b6d6c685138920a8a9d1e6205055cff023f3179": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "24e2195a49630a3b88efcb5227d7044651ffa1e483006af5ef3e41db492ca96a": { + "21bae751d95202679767297ac5a668ff2588c260e0c1d93e0ddb494a3c6c44d9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "45316309ea93c9d4d63ebd704488bd9b862cb9d30303b54ccbc668a45f1a9cfd": { + "ceb204983395689386573ade9d3496ac4c07fde14fd7dbc965b51654af834df1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "107e31005fc784e93f438721722b248ed66b8ed91d13eefca267786102fe5a81": { + "c2b69fdede340468e31ce79ee511016e1f3f15fceaf2228fb6bde3c034cb14d9": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ca603c439f5fa9a01c76f4febf58bf0b1034e100dd37fd1731c05f667409846e": { + "0a1f9c3aedb3a5721f4215c26cd403a7bca6d5fcba088d6ff8c5eb58a4168903": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -106,7 +106,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "bf801a713f62854431659efc54a0cb312dd601a8459d4390b13840fd7f414a02": { + "cf564971ee029d99592fad8d056f2d1cb13ff91e3e283dc2c16b3a644db90734": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -114,7 +114,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6c6bc2ace96267f8a839f207457b3d12b93f8211e7ce9a2f4ef9b2ddfc70412b": { + "482ab354d08783942a1cc1613d261773481b27647304272c9a895b3f82aef81b": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -122,7 +122,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "cd01bfe31bd7117a646dddc5be9732400e8ee0076cafd79c189115b8b3081950": { + "8ae623d95ec069f4263033a5c3c63cc41c8369a4ada5c2df1e72feb3472b3298": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -130,7 +130,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "fe4005d2eb3448257136861a88114cadcc85f06cb2ede14a36b581d4900e4a5e": { + "3ee628175449c6db9aeeb2b77f155f2af7f566ae1209ed32f564b4e74d37ffbe": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -138,7 +138,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "fb7e52e4c272d1e4e9602339c4f6a7094257194ea28d274314b8ca0a4aec639b": { + "1aaaeb79098a7feede64e211e53f192295e0d97db557223f5945bd869a9ec82c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -146,7 +146,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "de7cefc4a649b9c39f7fe31b49d5fa5f8ac6b792e2227717e0130dd127bbfd70": { + "1a9a9de8e21c848b2bfec22d14c770d8a3a176dd1ce39955a5b165bf1f851590": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -154,7 +154,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e084557c149a57fcf7e04041ad080c04586380baa9093a16dfee198a1ad51989": { + "c2dca5f665b034d94acdc589d0b6646e583561b3f05aeba0a0b6399297c8ccf1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -162,7 +162,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "f163106c94e153c44a0f9c882c90c6c5771e7df230fb847e3a9426bf18a8f312": { + "a014f0066d631022d1c7628bd1b990291f772710900955e0aab48543db7c3b2a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -170,7 +170,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "40aa0011039c7b0946bb1264199ba576db480b932c341fa5a224340056ec0976": { + "05ab2bff8b65224aff72672e380d66e022ff5f0c1b76e3231aaf172f6c82d467": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -178,7 +178,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "79ccc13c690d354291ea1fbee6c0b103d273130dd90bb6f3efe51840d1c562ae": { + "a10340fc40240dde6ba540065e6526b8c66b6fd78d116451b42201a9ba4c665d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -190,7 +190,7 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } @@ -1080,15 +1080,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json similarity index 93% rename from packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json rename to packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json index 96b428ade..27bfa4511 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-boundaries-number.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-boundaries-number", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "b4ba8411e13132261ecb4f57a72e7ca330d2618c536f5546590abb0cc25b9c47": { + "4151b6612247033495b8370b980c847c1efe5dc3ddb008a57aaf9e8afb97694d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "82796957263227e5d42ba913e864aca7aa0f6c06cf30ef1bd6a52cf771a5680e": { + "c3c464e47d3e87d433d2a0b6a5ea9e9cea9ffb65c31a7022fc4be4c51a3e3663": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9889dbdc0d0d8741c09f2d9e1f6b764e37995c66e76452502ff7a73398dc8fdb": { + "d9adf2748e7c2bb330f6b321204a4ab3d61aa555dbeaddcb3d79fb20383e3840": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "20c38d5ef57bd5c41cc52d311ab1147afabfcdbb2abbdaf47f9c1d5f71736248": { + "a3e931eb60a9b70284b948912691bbf4e1f757d1f52c90d1623413a4ed05eaf7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a989158eea675eb49b1f62e15c2205b1a1168d9ed769e90e16ba8f43e65963c2": { + "56a5f99336ac7daaa327d074e3c1cf1389f3b0e198d8b6b0dc4a0773f37466b0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "fd02ac0da0683586431856ba869c1ff07669b94244e6c8a58c7e1ae8dcb3a04d": { + "996e91f621b0091b21aaceea06330f3bcc8a4639939a0b3cafd38cdd2d732bde": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "0c4bdf647950f33d16dace93f197e1891b73d767a5bc3be0cf1e51a115775f63": { + "5b95fc3592f62934e978d8f18e998120d1012da96a4ae3c7a3bc832442914bce": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "bb03a68efa88e918862e8a481ee25893c059765384e7a0835b022f742ecffc05": { + "fb8ab09c443a1fef7f78aa00069193131bdddd5f754c69837da77dde8bd53ab4": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "596ef4687c078263b87dd17061e7471add9255b49814d72150bac75dce35787d": { + "13bc44aee0e07075a5f61e06bea48f9c7d7cfe240bbcf45d9922127c5fcbf57a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "920cb9612332743d61020ef481f4990bf0826040b3d087212a891115e9f30f22": { + "2b8ee396fbeba43a943135d2ea6962f4eee41f3af704609313b6241bf4911f20": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "62e88abd1cfc891df7f5703503077e82fb99db1e9be9b91d9a6b50333b4d1110": { + "9dec15ccf9649edff360ec3a223801aa6f804087a939b1d14b686a7fe34a0cbc": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a99b58377ef7b9e52d6e1c3befd549d61e1ca234eca3cd8d4720c16928d9a7a0": { + "b1aedbf12de210a49f962e5e8db09a065c2b328a7d2c48ce4c6e919325b9c0c5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -106,7 +106,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ff66a9c09d22765cf9397d3e156bce434a683325f21ab6df1c9b7359056d676a": { + "4a989c369519e210c4857cd50fd525e571cc89b7da322276ba05487c15b4849f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -114,7 +114,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9b15e9496c8eb9e19f2e8afc18ac298593ea8d45370e1fceb0f98b0f47e28fdb": { + "313de806bdcd55e96b13d12c28260bc5238057d4ed2a8ecc0294f8c7e234fa01": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -122,7 +122,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e0d4ba65f888bbf2b204e1ed4cc02d23b0e0c3f8a0f4f22b51501c7532d4781f": { + "1f5409b64eb99b424819230ddfb333b5f399a911840ab6b36f0a0938a438af25": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -130,7 +130,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9b5089501d028a056aae6a5de88452f98f79340ba32f765a3950c6d8c0a82a9d": { + "61f7deca99e29b35ab18cdf82001dcf64ea3b04caaa9fc751d835f1c70d25fd2": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -138,7 +138,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "1706e7b05d435e118cb4dee1540e2109fd7a7b1b19b08fceec0cfebc8fc0abc1": { + "06503c386b2c84e0299e7a692baecdecf2b19698b90f13b89df7d4e309d43870": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -146,7 +146,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "3769e087d36053e2971961b47640ab9b7397f02b1eddda5d4e90819ef1b1f5bb": { + "6d9de4417c04ad9908dc846b1a9ae5c6c2fe214e7aca7e2dd3480d82abd46a5f": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -154,7 +154,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e0cff618c57a7aca3a66fbbb36b72aa53a4d4b7a08a5c67a0e25ffd564cea395": { + "1b553f209415af2ef3dd59e7f7383e270e5090b9811e319c39645a51d58ec26c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -162,7 +162,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4d204756440205698d05663c5a511d991d8e7ad64f54b1a257034b8e2c52f80f": { + "4ccf6d4d18107b748ae8ec301e66adb190f15bd77e3bf23246d8994787c8a40c": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -170,7 +170,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4c741509a1ac866498f61619846c2cdec7fd84bbc362ee6ff3f8d563f2156fc8": { + "9de54af153484d9db86d593c9a0c9443f7c5a88881964452b69b7f2d8a878c01": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -178,7 +178,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c9bbbec289c2c83e830e9bffd2ff577daf18bb71371cff7c08a5372ed860d957": { + "52b56cf56113aee71339a1256252ae52741aa5e666d2a5ca3ae3ad194cbb83f5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -190,7 +190,7 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } @@ -1080,15 +1080,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json similarity index 80% rename from packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json rename to packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json index fdbedade4..cb5d7f405 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-float.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-float", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "43dcecc88807de972a354bd0b96946419ccf086a3cb1abcaef10a796129ffb7e": { + "6711bb1e8b093b846ea439322c667bce9ca0568b0f0d4320e05c236c9ead11ce": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "ad5896fa25ecdb06cc985d99e469f2b867419c296d99142ba53b7a4ab11d0167": { + "dd28ccc7dc267c0927246a422010adbf6e81bb5b0e8fa6a3f9c5c999dcfa8cac": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5f920ae1126d6bf5dd3c7cde59b1604dfbae53d4ad4686a548871a4baec00f22": { + "9662b5e2c026477337a6a1b952ca1da98116ea3393c14ad81bf3b0e6f7705eae": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "a710178c1d9213803a5ff5f56146c93908327ac97407d76c6139dfdcd9a4012f": { + "6a126c45f099dc1465fb20627b24f64632aae36028757b78b361873c454d566e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "b81af3ac1aba6361e1eb0951f2e1babd617d2218827ce4a14f2c8a5f8005b885": { + "7a331b89b73d723922e64da680ad3048d381db62fd6f9139eb9e57ea43569156": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "de8b6921dbff87fdf7182b8a948b9644aa4db01a9dbbda9749a0774f189746cc": { + "e152b1e1e80ca9edabd8cfd196b0084ee4728b8eb6b85c1de743705b8ec3f1c5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9a8d577fc6f24f49f804fa068b9a7170c9328c0def19a2b2312c9f59b5dfab02": { + "968e09a8af812ed8d7fc18b21f59d5b7623d5e1ae6fc8f5a141443f026b11aa7": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "82077f26295c6ead94d7a7a7554ed4e143a4af711f8deb94b8a8a711d032c4f2": { + "b4b714689b3211e62d2fbde91099071f193e00c955f5a96a6ffa087b2e6bfa98": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "8bf2c56f7653627e757f8263815d7408dbcbb6fb86d71d55402e9800f185ed22": { + "a978ec7a2487b240242a05119affc348f41d0d9fbdce334665c4090c2f4ab1fd": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "cef1691308f6e520bbdbc8575683d17bc814f52af8ef42522d6a5bf0aee7e861": { + "3860419b6dc9dc69573f100ccbf51c008e45a8a9b6a62243ff02b869f4e86b47": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "8b79dc5672bf5c07c2a64b68cd885ba18db98b52639d349283ac6888477326a6": { + "a303e6fa7efedf997c87edfc2d79872c95d3e1a6c5310ccfe65d0826f700ab55": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c0b84d2e8690addb339fd433c9cc449d6da6632ea8398d47e9bfebde442247a0": { + "41a0cf77c6270e0e6f3941ab2fa80f712b1c916d13793ddd36a99363c07ad885": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", @@ -110,14 +110,14 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } }, "evaluations": [ { - "flag": "obfuscation-numeric-float-0", + "flag": "numeric-float-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -128,7 +128,7 @@ } }, { - "flag": "obfuscation-numeric-float-1", + "flag": "numeric-float-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -139,7 +139,7 @@ } }, { - "flag": "obfuscation-numeric-float-2", + "flag": "numeric-float-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -150,7 +150,7 @@ } }, { - "flag": "obfuscation-numeric-float-3", + "flag": "numeric-float-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -161,7 +161,7 @@ } }, { - "flag": "obfuscation-numeric-float-4", + "flag": "numeric-float-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -172,7 +172,7 @@ } }, { - "flag": "obfuscation-numeric-float-5", + "flag": "numeric-float-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -183,7 +183,7 @@ } }, { - "flag": "obfuscation-numeric-float-6", + "flag": "numeric-float-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -212,7 +212,7 @@ ] }, { - "flag": "obfuscation-numeric-float-7", + "flag": "numeric-float-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -223,7 +223,7 @@ } }, { - "flag": "obfuscation-numeric-float-8", + "flag": "numeric-float-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -252,7 +252,7 @@ ] }, { - "flag": "obfuscation-numeric-float-9", + "flag": "numeric-float-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -263,7 +263,7 @@ } }, { - "flag": "obfuscation-numeric-float-10", + "flag": "numeric-float-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -292,7 +292,7 @@ ] }, { - "flag": "obfuscation-numeric-float-11", + "flag": "numeric-float-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -309,55 +309,55 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-0" + "key": "numeric-float-0" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-1" + "key": "numeric-float-1" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-2" + "key": "numeric-float-2" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-3" + "key": "numeric-float-3" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-4" + "key": "numeric-float-4" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-5" + "key": "numeric-float-5" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-7" + "key": "numeric-float-7" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-9" + "key": "numeric-float-9" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-11" + "key": "numeric-float-11" } } ], @@ -365,49 +365,49 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-0" + "key": "numeric-float-0" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-1" + "key": "numeric-float-1" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-2" + "key": "numeric-float-2" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-3" + "key": "numeric-float-3" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-4" + "key": "numeric-float-4" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-5" + "key": "numeric-float-5" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-6" + "key": "numeric-float-6" }, "evaluation_count": 1, "_include": [ @@ -433,14 +433,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-7" + "key": "numeric-float-7" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-8" + "key": "numeric-float-8" }, "evaluation_count": 1, "_include": [ @@ -466,14 +466,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-9" + "key": "numeric-float-9" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-10" + "key": "numeric-float-10" }, "evaluation_count": 1, "_include": [ @@ -499,7 +499,7 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-float-11" + "key": "numeric-float-11" }, "evaluation_count": 1 } @@ -508,15 +508,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json similarity index 79% rename from packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json rename to packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json index 4b638c491..b1ec8e611 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-integer.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-integer", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "72101f81c2d474893bb90b2e9fbece5750b83659171b75d423eba3c8caa09af8": { + "6f6faa00700939aa0c395e059d8dea1cc48b2c5a20eb2ef5bc7abfeb551e2d6a": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "158853f6fec44bdd14c7dcf732538d1932393333da0d38e5b1cf389febab173a": { + "6418f8b5f4df9814bfd25f30a8ef6865929075706f260ca4f48f5334f5541c89": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5e716e405677ef15a943d1e3ec8577a5436ad2b08b619801fa115e3f6056e7e1": { + "0f2ba6c096112e6b701607e345fc3a06a2a0d93710715cea905546b69b536ec0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "55aa24315699bb2e6b22e1e60dbc59588628d4987393d1709e63283f740da782": { + "ddd11d6e8fd3dfc9734f307630745484bb411a3278c36ad0dd7c681f91edcde5": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "11dbbd2cc435f0d8521f492798f5cdeda39b44612afb5b2e8dbbca244eac8afb": { + "b1108b8a690b2ca3dd55e74294ed8bfdf1496b88ed4b3cb87abf27199c6b819e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5f600bc0a426abe99c1d0f650b9b8eb243179105c4a73a59f696b99967088fcd": { + "01ede755b925df7f56ae33a0bcc4d822a0495b602eea740c5b8570c025527afa": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "422fd4f38b4009a3443ac2334e4687d21e2eb02d7614a3eec83c7630585d7f4e": { + "fc310ab0e0bd16d08c9b769db925a177fa89b3f539d75b01ef8dd319c427fccf": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "77e8ec1779ffe7a8edd77b8c84358cae99993a9898b4c0cadf2336a88ae99ccd": { + "ad9ca46d7714846bd2a813e74d2af97e1cd25df3b0242f1575db2083f6f9d8b1": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6779f3038ba17b00bc22bd42f0fb7078c7a3cf7843adc7884e269ad0b33a0180": { + "35eb2ca6e144c429db174c281e9e55d2b9b4e9d819a159203bbdf577354e4033": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "7224ebe3cb4e571ec923f2624b68bd3e183c199258e5523e6456b62e9254f8e5": { + "27bc83275f0f9a5e871d2e4c5af47829d9f7b72c3e5f1f224fbafe3cef986c81": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "9483e1046878fedc712a9345a3e8406df5b24d42f7e0d4500ff8cc9fb1c9a161": { + "19415991350c00c660f9d74c206db9ec7d9fbde7c8a8cbdcfd6775911e40be6b": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "e3e9c957b3953d38da8ada7fadcb6bea2ce78691d7cc34dbc2c75297ee4c5a95": { + "a556037a239ae8b7c3789f59013e015ebf29686de47bce8aa1bb17f99569a4e8": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", @@ -110,14 +110,14 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } }, "evaluations": [ { - "flag": "obfuscation-numeric-integer-0", + "flag": "numeric-integer-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -128,7 +128,7 @@ } }, { - "flag": "obfuscation-numeric-integer-1", + "flag": "numeric-integer-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -139,7 +139,7 @@ } }, { - "flag": "obfuscation-numeric-integer-2", + "flag": "numeric-integer-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -150,7 +150,7 @@ } }, { - "flag": "obfuscation-numeric-integer-3", + "flag": "numeric-integer-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -161,7 +161,7 @@ } }, { - "flag": "obfuscation-numeric-integer-4", + "flag": "numeric-integer-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -172,7 +172,7 @@ } }, { - "flag": "obfuscation-numeric-integer-5", + "flag": "numeric-integer-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -183,7 +183,7 @@ } }, { - "flag": "obfuscation-numeric-integer-6", + "flag": "numeric-integer-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -212,7 +212,7 @@ ] }, { - "flag": "obfuscation-numeric-integer-7", + "flag": "numeric-integer-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -223,7 +223,7 @@ } }, { - "flag": "obfuscation-numeric-integer-8", + "flag": "numeric-integer-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -252,7 +252,7 @@ ] }, { - "flag": "obfuscation-numeric-integer-9", + "flag": "numeric-integer-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -263,7 +263,7 @@ } }, { - "flag": "obfuscation-numeric-integer-10", + "flag": "numeric-integer-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -292,7 +292,7 @@ ] }, { - "flag": "obfuscation-numeric-integer-11", + "flag": "numeric-integer-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -309,55 +309,55 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-0" + "key": "numeric-integer-0" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-1" + "key": "numeric-integer-1" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-2" + "key": "numeric-integer-2" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-3" + "key": "numeric-integer-3" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-4" + "key": "numeric-integer-4" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-5" + "key": "numeric-integer-5" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-7" + "key": "numeric-integer-7" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-9" + "key": "numeric-integer-9" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-11" + "key": "numeric-integer-11" } } ], @@ -365,49 +365,49 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-0" + "key": "numeric-integer-0" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-1" + "key": "numeric-integer-1" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-2" + "key": "numeric-integer-2" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-3" + "key": "numeric-integer-3" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-4" + "key": "numeric-integer-4" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-5" + "key": "numeric-integer-5" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-6" + "key": "numeric-integer-6" }, "evaluation_count": 1, "_include": [ @@ -433,14 +433,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-7" + "key": "numeric-integer-7" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-8" + "key": "numeric-integer-8" }, "evaluation_count": 1, "_include": [ @@ -466,14 +466,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-9" + "key": "numeric-integer-9" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-10" + "key": "numeric-integer-10" }, "evaluation_count": 1, "_include": [ @@ -499,7 +499,7 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-integer-11" + "key": "numeric-integer-11" }, "evaluation_count": 1 } @@ -508,15 +508,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json similarity index 79% rename from packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json rename to packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json index 925306497..7df21eac5 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/obfuscation-numeric-number.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json @@ -1,6 +1,6 @@ { "name": "obfuscation-numeric-number", - "description": "Lossless numeric reads are independent of the numeric wire label.", + "description": "Generated obfuscated companion: Lossless numeric reads are independent of the numeric wire label.", "context": { "targetingKey": "fixture-subject", "attributes": {} @@ -10,7 +10,7 @@ "attributes": { "createdAt": "2026-01-01T00:00:00Z", "flags": { - "5d1d3c167fda7f3ecadb68da40228431ed0b74ec08597fc57398fbf7f253f0d6": { + "5a1d7afff3fd522b50e8891f7af14267bdd253a1455ed12bc92188f78e12033d": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -18,7 +18,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4dcb0659aaeb33eb329143c5ed994c52800521968edafd58c95c50377873ccc4": { + "cbc15a556f7e611fa26f06f6a6334ee91ad5d6a344496452557db41ed992bc25": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -26,7 +26,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "8023572892cfed9fb5ee411c451eab7480329f32331f3845e76234275bede04d": { + "f1c431305b153a3030fadebc0057e634b6162cd6acda9744a763082b1197c740": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -34,7 +34,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "d15970ae43ef67b0a9844f04cd8680567400e12340d545aa608a0cc9b89bed95": { + "7b3cbab857c42d32035705aa891dd7a2fbadcbe1fc0f1e28f3611076a6aa7945": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -42,7 +42,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "4373eda204ffa94cc5c15d29bd5a33b828033f4d2382f708801a7ea5c298b286": { + "acdfe2ec44b3eb617b86da9a49166e3abecbc85c00951e548516cfd748056403": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -50,7 +50,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "5a95ab1a979318a8d113fcdb44a9a5262f0f99a6fb58e0f782b1ac5dbc88f1ef": { + "3af8ba8e93c58c248ed0d943b8bfbc630236bbbeca96be121efc46ed8da4b9b0": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -58,7 +58,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "dceef8e665186596db59e1c8bf2887adf20969530b48091e1bcf3517bf804a7b": { + "efb49302883b084e49d51cb2f41fd795224a571d182536e203f6ab28e6648b77": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -66,7 +66,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "6b8f85c278e445167be847d3a94a91c1865221df7cc63dc8f31aacbb9454e430": { + "c38a503ec9a1c032f0a073e23d6d0ede2eccf245aac33f9db02a5374728c810e": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -74,7 +74,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "81da1069b7cb802d039c7cd27d4c0f4db2515a5c64dd411a522d6f300592c31c": { + "499aef4e9d9d7df120a79816e9406b5925664e851fd638a129f525588e8a9385": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -82,7 +82,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "0a2bcee9cec623b02ca219d7b3f15415767bb630f6bcf9af75fda36c2a689f64": { + "b004cb3fb7dbb1846ba17f461b1a0931cd52d9ffde2c5419d51c0b47e42baf99": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -90,7 +90,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "be4ecda1ccdb165dc68fcffc30f021c7e83a88b8a2283784498ac761385f26d0": { + "d858ab64de6a97983f95792030011b2dd635938aaefd90fee12b37ef67e9dac3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -98,7 +98,7 @@ "reason": "TARGETING_MATCH", "doLog": true }, - "c4f3ba7ee684fddd93335901ae9b0af3959704d06b5a028449b08854d5384e3d": { + "3ece2688265c5c214d2904619cf0294042830dc10d72dae32e50050c7cded8c3": { "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", @@ -110,14 +110,14 @@ "obfuscated": true, "obfuscation": { "scheme": "flag-key-sha256-v1", - "salt": "00112233445566778899aabbccddeeff" + "salt": "000102030405060708090a0b0c0d0e0f" } } } }, "evaluations": [ { - "flag": "obfuscation-numeric-number-0", + "flag": "numeric-number-0", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -128,7 +128,7 @@ } }, { - "flag": "obfuscation-numeric-number-1", + "flag": "numeric-number-1", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -139,7 +139,7 @@ } }, { - "flag": "obfuscation-numeric-number-2", + "flag": "numeric-number-2", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -150,7 +150,7 @@ } }, { - "flag": "obfuscation-numeric-number-3", + "flag": "numeric-number-3", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -161,7 +161,7 @@ } }, { - "flag": "obfuscation-numeric-number-4", + "flag": "numeric-number-4", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -172,7 +172,7 @@ } }, { - "flag": "obfuscation-numeric-number-5", + "flag": "numeric-number-5", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -183,7 +183,7 @@ } }, { - "flag": "obfuscation-numeric-number-6", + "flag": "numeric-number-6", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -212,7 +212,7 @@ ] }, { - "flag": "obfuscation-numeric-number-7", + "flag": "numeric-number-7", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -223,7 +223,7 @@ } }, { - "flag": "obfuscation-numeric-number-8", + "flag": "numeric-number-8", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -252,7 +252,7 @@ ] }, { - "flag": "obfuscation-numeric-number-9", + "flag": "numeric-number-9", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -263,7 +263,7 @@ } }, { - "flag": "obfuscation-numeric-number-10", + "flag": "numeric-number-10", "variationType": "integer", "defaultValue": -99, "expectedResult": { @@ -292,7 +292,7 @@ ] }, { - "flag": "obfuscation-numeric-number-11", + "flag": "numeric-number-11", "variationType": "float", "defaultValue": -99.5, "expectedResult": { @@ -309,55 +309,55 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-0" + "key": "numeric-number-0" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-1" + "key": "numeric-number-1" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-2" + "key": "numeric-number-2" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-3" + "key": "numeric-number-3" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-4" + "key": "numeric-number-4" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-5" + "key": "numeric-number-5" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-7" + "key": "numeric-number-7" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-9" + "key": "numeric-number-9" } }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-11" + "key": "numeric-number-11" } } ], @@ -365,49 +365,49 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-0" + "key": "numeric-number-0" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-1" + "key": "numeric-number-1" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-2" + "key": "numeric-number-2" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-3" + "key": "numeric-number-3" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-4" + "key": "numeric-number-4" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-5" + "key": "numeric-number-5" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-6" + "key": "numeric-number-6" }, "evaluation_count": 1, "_include": [ @@ -433,14 +433,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-7" + "key": "numeric-number-7" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-8" + "key": "numeric-number-8" }, "evaluation_count": 1, "_include": [ @@ -466,14 +466,14 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-9" + "key": "numeric-number-9" }, "evaluation_count": 1 }, { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-10" + "key": "numeric-number-10" }, "evaluation_count": 1, "_include": [ @@ -499,7 +499,7 @@ { "_count": 1, "flag": { - "key": "obfuscation-numeric-number-11" + "key": "numeric-number-11" }, "evaluation_count": 1 } @@ -508,15 +508,15 @@ "_include": [ { "platform": "ios", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "android", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." }, { "platform": "flutter", - "reason": "Requires native numeric precision or supported obfuscation." + "reason": "Supports obfuscation and the source fixture." } ] } diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json new file mode 100644 index 000000000..eb4655e1e --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json new file mode 100644 index 000000000..7862719d2 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json new file mode 100644 index 000000000..d7950c8e6 --- /dev/null +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/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/packages/core/src/flags/__tests__/fixtures/precomputed/index.json b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json index d3d7c926e..6037badcb 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/index.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/index.json @@ -18,13 +18,26 @@ "cases/evaluator/object-nested-deep.json", "cases/evaluator/special-characters-in-keys.json", "cases/evaluator/type-mismatch-all-types.json", + "cases/obfuscation/generated/emissions/obfuscation-all-doLog-false.json", + "cases/obfuscation/generated/emissions/obfuscation-duplicate-evaluation.json", + "cases/obfuscation/generated/emissions/obfuscation-serial-id-on-exposure.json", + "cases/obfuscation/generated/evaluator/obfuscation-all-types-success.json", + "cases/obfuscation/generated/evaluator/obfuscation-defaults-and-emission-gates.json", + "cases/obfuscation/generated/evaluator/obfuscation-empty-flags-response.json", + "cases/obfuscation/generated/evaluator/obfuscation-falsy-values.json", + "cases/obfuscation/generated/evaluator/obfuscation-integer-precision-native-only.json", + "cases/obfuscation/generated/evaluator/obfuscation-multi-flag-set.json", + "cases/obfuscation/generated/evaluator/obfuscation-multiple-reasons.json", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json", + "cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json", + "cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json", + "cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json", "cases/obfuscation/obfuscation-keys.json", - "cases/obfuscation/obfuscation-numeric-boundaries-float.json", - "cases/obfuscation/obfuscation-numeric-boundaries-integer.json", - "cases/obfuscation/obfuscation-numeric-boundaries-number.json", - "cases/obfuscation/obfuscation-numeric-float.json", - "cases/obfuscation/obfuscation-numeric-integer.json", - "cases/obfuscation/obfuscation-numeric-number.json", "cases/obfuscation/obfuscation-reject-false-with-descriptor.json", "cases/obfuscation/obfuscation-reject-missing-descriptor.json", "cases/obfuscation/obfuscation-reject-missing-marker.json", From 8898519f40504adc9b883f48731c0ced437e45e7 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 18:44:14 -0500 Subject: [PATCH 7/8] FFL-3568: Exclude Flags test sources from the npm package --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 26669404c..cc486b773 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,7 +18,7 @@ "main": "lib/commonjs/index", "files": [ "src/**", - "!src/flags/__tests__/fixtures/**", + "!src/flags/__tests__/**", "lib/**", "scripts/**", "android/build.gradle", From 7b4443b9594a48e38d47f80507348b6366de85b5 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Tue, 3 Nov 2026 19:05:21 -0500 Subject: [PATCH 8/8] FFL-3568: Refresh integral floating-value fixtures --- .../__tests__/fixtures/precomputed/SOURCE.json | 14 +++++++------- .../precomputed/cases/evaluator/numeric-float.json | 2 +- .../cases/evaluator/numeric-integer.json | 2 +- .../cases/evaluator/numeric-number.json | 2 +- .../evaluator/obfuscation-numeric-float.json | 2 +- .../evaluator/obfuscation-numeric-integer.json | 2 +- .../evaluator/obfuscation-numeric-number.json | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json index 13c31d7d3..cd406db83 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/SOURCE.json @@ -1,6 +1,6 @@ { "repository": "https://github.com/DataDog/ffe-system-test-data.git", - "commit": "8dcc09a53dfb654cb0b2de8cda96919710f074e6", + "commit": "faa89edca7043428824ef65050e3293113100624", "files": { "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4", "NOTICE": "a5245f10dbda39cc65bad11ab3a6022b9e5c05b259da3f7aa0302d932f31083e", @@ -18,9 +18,9 @@ "cases/evaluator/numeric-boundaries-float.json": "f986d58267e590f1be98dc9fe4b5151852c9565028469735b30885a6f369eaa9", "cases/evaluator/numeric-boundaries-integer.json": "4569c35d977fff5c77134b9e0342b740b7a18e4cf01e461f0c8b5ed9c250708b", "cases/evaluator/numeric-boundaries-number.json": "ab1e965ac0b335bf56442c0bfa262ba62214b3118a1c1cda6512f38b38717294", - "cases/evaluator/numeric-float.json": "d889bf0107d350e47fde325836caf6690c643b779c51fc3812f948e22a8c67ce", - "cases/evaluator/numeric-integer.json": "4d080f862f53c6d7fab3670a3793edb1ddd9e566a68e262e59abfaff689db39d", - "cases/evaluator/numeric-number.json": "cf2aafebf871b57f0f3a74abf0126d6a4f35cb18d78602611ee4419e6f9235d9", + "cases/evaluator/numeric-float.json": "8a61435912d1ad7c25dc34bfecb030f4d7583f5b4c40ad4f8659b9cce50d93fc", + "cases/evaluator/numeric-integer.json": "b90b486688aa5462028175fe661d4d90bcf933089b63b8092c0004cf181f895f", + "cases/evaluator/numeric-number.json": "36401b78a6ddb09e4fb81c93729325330d090ef58e4be2dc3531e30b8d8da43d", "cases/evaluator/object-nested-deep.json": "22f8f0c516cfe0456d34697f29bbf39d7736559e0b188de4671d0fee6545fd3d", "cases/evaluator/special-characters-in-keys.json": "07e24ba1a407ef9604af0bc5ab3f8faafe199dd83db94d96d0e3d689430054ce", "cases/evaluator/type-mismatch-all-types.json": "0904425126ae64794a8c67d13d4bab759a88abc75c27c99f71c514babb9279a3", @@ -37,9 +37,9 @@ "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-float.json": "3ad1a96dffe7d376e8c381b6b089fb7cd30423b39d8f7f4006fa86fdab3a78bd", "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-integer.json": "01269d0992978348fe0cba509978ba4b10e65a7bb03284847efb1be1a014d7da", "cases/obfuscation/generated/evaluator/obfuscation-numeric-boundaries-number.json": "df03044f0726dfe353b172ae75daf205fa4280a76ec05ac4dfc310f51ca186f6", - "cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json": "702674897e5c457c070e0abdb112a38e5c1274d7e21ae314f5e2dfa805320a41", - "cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json": "13a177de29ae708fd8361c095279d78166ae82d2fbe8d25804d5790d527bd3d1", - "cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json": "3e8a78b7016acbcce301e1f437f145f9a24737989d70bda00fba2f9e8ffb2f11", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json": "c79fcdca8b38fc24098489de8c85ba78412a8fccbe731b89eae6ac885642bb22", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json": "aa1936cf515645f546de081c9769f9f67b99ff68a178eaa50df482a1b7ecf1d0", + "cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json": "50aee440e18d7192fe2550dc9782229edcb22535122cca84e3891def76594b0e", "cases/obfuscation/generated/evaluator/obfuscation-object-nested-deep.json": "ff2ba07eb266db0669f0f86926060d268f3242d437264b5d4663f259317d6bc1", "cases/obfuscation/generated/evaluator/obfuscation-special-characters-in-keys.json": "fd4f9efb1eb115e505c81a30ae82c51766747cbc26ae5a36dda961a4cfeb2cd8", "cases/obfuscation/generated/evaluator/obfuscation-type-mismatch-all-types.json": "6dc2320ceb7013825dbda712ed789689a0bd3b36294a741a8bc23371bbe7db91", diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json index dd8b3cb87..43e5f8766 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-float.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json index e1ce07409..2b4e63cfb 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-integer.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json index c97208a84..498ff4afe 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/evaluator/numeric-number.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json index cb5d7f405..bf65bb4b9 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-float.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "float", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json index b1ec8e611..e61afa1d8 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-integer.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "integer", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true }, diff --git a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json index 7df21eac5..6ea6bdf92 100644 --- a/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json +++ b/packages/core/src/flags/__tests__/fixtures/precomputed/cases/obfuscation/generated/evaluator/obfuscation-numeric-number.json @@ -30,7 +30,7 @@ "allocationKey": "allocation", "variationKey": "variant", "variationType": "number", - "variationValue": 5, + "variationValue": 5.0, "reason": "TARGETING_MATCH", "doLog": true },