Skip to content

docs: update remote feature flag threshold API - #176

Closed
asalsys wants to merge 1 commit into
mainfrom
docs/remote-feature-flag-threshold-api
Closed

docs: update remote feature flag threshold API#176
asalsys wants to merge 1 commit into
mainfrom
docs/remote-feature-flag-threshold-api

Conversation

@asalsys

@asalsys asalsys commented Jul 2, 2026

Copy link
Copy Markdown

Summary

Updates remote feature flags documentation to reflect the RemoteFeatureFlagController API changes in MetaMask/core#9325, MetaMask/core#9289:

  • Threshold flags return the selected value directly from remoteFeatureFlags (no { name, value } wrapper).
  • Selected group names are exposed via featureFlagThresholdGroups when the winning threshold entry includes name.
  • Documents optional idType on threshold entries (canonical by default, metametrics for legacy MetaMetrics segmentation).
  • Updates the local override example to use a direct flag value.

Test plan

  • Reviewed markdown for accuracy against RemoteFeatureFlagController behavior in core PR #9325

Made with Cursor

Document featureFlagThresholdGroups, canonical ID as the default threshold
segmentation identifier, and removal of the legacy { name, value } wrapper.

Co-authored-by: Cursor <cursoragent@cursor.com>
@asalsys
asalsys requested review from DDDDDanica and removed request for DDDDDanica July 2, 2026 14:08
@asalsys asalsys closed this Jul 2, 2026
@asalsys
asalsys deleted the docs/remote-feature-flag-threshold-api branch July 2, 2026 14:10
@asalsys
asalsys restored the docs/remote-feature-flag-threshold-api branch July 2, 2026 14:41
@asalsys asalsys reopened this Jul 2, 2026
@asalsys asalsys closed this Jul 2, 2026
@asalsys
asalsys deleted the docs/remote-feature-flag-threshold-api branch July 2, 2026 14:46
pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jul 2, 2026
…y and track threshold groups (MetaMask#9289)

## Summary

- **BREAKING:** Threshold feature flags now return the selected `value`
directly from `remoteFeatureFlags` instead of a `{ name, value }`
wrapper object.
- Add optional `featureFlagThresholdGroups` state field (`Record<string,
string>`) that maps feature flag names to their selected threshold group
name when the selected threshold entry includes `name`.
- Remove `normalizeThresholdValue` and the legacy `{ name, value }`
wrapper fallback.

## References

- Fixes
[MCWP-655](https://consensyssoftware.atlassian.net/browse/MCWP-655)

## Motivation

Separating the processed flag value from threshold group metadata makes
it easier for consumers to use threshold flags as plain values
(booleans, objects, etc.) without unwrapping. The new
`featureFlagThresholdGroups` field provides explicit access to A/B group
assignment when the selected threshold entry includes `name`.

Entries that only define `thresholdName` (for example in
`thresholdVersion: 2` configs) still resolve to the selected `value`,
but do not populate `featureFlagThresholdGroups` because that field is
driven by `name`.

## Migration

Consumers that previously read threshold group names from
`remoteFeatureFlags[flagName].name` should instead:

1. Read the flag value directly from `remoteFeatureFlags[flagName]`.
2. Read the threshold group name from
`featureFlagThresholdGroups[flagName]` when available (populated when
the selected threshold entry includes `name`).

**Before:**
```ts
const flag = remoteFeatureFlags.myThresholdFlag;
// { name: 'groupB', value: { enabled: true } }
const groupName = flag.name;
const value = flag.value;
```

**After:**
```ts
const value = remoteFeatureFlags.myThresholdFlag;
// { enabled: true }

const groupName = featureFlagThresholdGroups.myThresholdFlag;
// 'groupB'
```

## Related

- Documentation:
[MetaMask/contributor-docs#176](MetaMask/contributor-docs#176)

## Test plan

- [x] `yarn workspace @metamask/remote-feature-flag-controller run test`
— all tests pass with 100% coverage
- [x] Threshold flags return the selected `value` directly from
`remoteFeatureFlags`
- [x] Threshold flags populate `featureFlagThresholdGroups` when the
selected entry includes `name`
- [x] Threshold flags with only `thresholdName` return the value
directly and leave `featureFlagThresholdGroups` empty
- [x] Stale `featureFlagThresholdGroups` entries are cleaned up when
flags are removed from the server response

[MCWP-655]:
https://consensyssoftware.atlassian.net/browse/MCWP-655?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking change to the public shape of threshold flag values affects
all consumers of `remoteFeatureFlags`; persisted state gains a new field
but assignment logic is otherwise unchanged.
> 
> **Overview**
> **BREAKING:** Threshold/A/B flags in `remoteFeatureFlags` now expose
the selected entry’s **`value` only** (string, boolean, object, etc.)
instead of a `{ name, value }` wrapper. Group assignment moves to a new
optional persisted state field **`featureFlagThresholdGroups`**
(`flagName → group name`) when the winning threshold item has a `name`.
> 
> Processing drops **`normalizeThresholdValue`** and
**`ThresholdVersion`** branching—legacy wrappers and “direct value vs
wrapper” version handling are gone; threshold arrays are resolved the
same way regardless of `thresholdVersion`. On each fetch,
**`featureFlagThresholdGroups` is rebuilt** from the current server
payload so entries for removed flags are cleared (covered by tests).
> 
> **Migration:** read values from `remoteFeatureFlags[flag]`; read group
names from `featureFlagThresholdGroups[flag]` when present.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
98b74a2. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mark Stacey <markjstacey@gmail.com>
basgys pushed a commit to basgys/metamask-core that referenced this pull request Aug 20, 2026
…MetricsFlags with canonical ID default (MetaMask#9325)

## Summary

- Add required `getCanonicalProfileId` constructor callback and
`metaMetricsFlags` map for threshold flag segmentation.
- Threshold flags use canonical profile ID by default; flags whose names
are present in `metaMetricsFlags` use MetaMetrics ID instead.
- Generalize threshold calculation to hash the selected segmentation ID
while preserving the existing deterministic bucketing behavior.
- Pass the new segmentation options through `@metamask/wallet` remote
feature flag controller initialization.

## Segmentation Behavior

| Flag configuration | Identifier used | Constructor callback |
| --- | --- | --- |
| Flag name absent from `metaMetricsFlags` | Canonical profile ID |
`getCanonicalProfileId()` |
| Flag name present in `metaMetricsFlags` | MetaMetrics ID |
`getMetaMetricsId()` |

If the selected identifier is empty, threshold arrays are preserved
as-is and not processed.

Example threshold config using canonical segmentation by default:

```json
{
  "myCanonicalFlag": [
    {
      "name": "groupA",
      "scope": { "type": "threshold", "value": 0.5 },
      "value": "valueA"
    },
    {
      "name": "groupB",
      "scope": { "type": "threshold", "value": 1.0 },
      "value": "valueB"
    }
  ]
}
```

Example controller setup for a flag that should keep MetaMetrics
segmentation:

```ts
new RemoteFeatureFlagController({
  // ...
  getMetaMetricsId,
  getCanonicalProfileId,
  metaMetricsFlags: {
    legacyMetaMetricsFlag: true,
  },
});
```

## Migration

Consumers must pass `getCanonicalProfileId` for threshold flags that
should use canonical profile segmentation, which is now the default for
flags absent from `metaMetricsFlags`.

Existing threshold flags that must keep MetaMetrics-based bucketing
should be listed in `metaMetricsFlags` and continue to provide
`getMetaMetricsId`.

## Test plan

- [x] `yarn workspace @metamask/remote-feature-flag-controller run jest
--no-coverage src/utils/user-segmentation-utils.test.ts
src/remote-feature-flag-controller.test.ts`
- [x] Threshold flags absent from `metaMetricsFlags` use
`getCanonicalProfileId` for segmentation
- [x] Threshold flags present in `metaMetricsFlags` use
`getMetaMetricsId` for segmentation
- [x] Threshold arrays are preserved when the selected identifier is
empty
- [x] Threshold cache entries use the selected segmentation ID and stale
entries are cleaned up when flags are removed from the server response

## Related

- Documentation:
[MetaMask/contributor-docs#176](MetaMask/contributor-docs#176)
- Ticket - https://consensyssoftware.atlassian.net/browse/MCWP-662


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Breaking constructor and experiment-assignment change: existing
threshold flags will re-bucket unless listed in `metaMetricsFlags`. Not
auth or payment logic, but it can change live A/B group membership.
> 
> **Overview**
> **Breaking:** threshold flags now bucket by **canonical profile ID**
by default instead of MetaMetrics ID. `RemoteFeatureFlagController`
requires `getCanonicalProfileId`; flags listed in optional
`metaMetricsFlags` still use `getMetaMetricsId`.
> 
> Hash-based assignment hashes the chosen segmentation ID. If that ID is
empty, the threshold array is left unprocessed. Explicit MetaMetrics ID
targeting is unchanged. Threshold cache keys and stale-entry cleanup
cover both identifiers.
> 
> `@metamask/wallet` forwards the new options (optional; empty-string
default for canonical ID). Consumers that need the old MetaMetrics
bucketing must list those flag names in `metaMetricsFlags`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
09b141f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cal-L <cal.leung@consensys.net>
Co-authored-by: Cal Leung <cleun007@gmail.com>
Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant