From 18f496e012a3b9dc28c3098fa5323fdc67ab3bae Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:29:48 -0400 Subject: [PATCH 1/9] docs: amend ADR 0009 to include provider domain in cache key Signed-off-by: Jonathan Norris --- ...al-storage-for-static-context-providers.md | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index 06b7e84..667621f 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -6,6 +6,8 @@ Date: 2026-03-06 Accepted +Proposed amendment (2026-06-19): include the provider's bound `domain` in the cache key, and soft-propose tying the cache key to the OFREP resource (URL, and optionally auth) it fetches from. See [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). + ## Context OFREP static-context providers evaluate all flags in one request and then serve evaluations from a local cache. @@ -34,17 +36,19 @@ The persisted entry should include: - the bulk evaluation payload - the associated `ETag`, if one was returned -- a `cacheKeyHash` equal to `hash(targetingKey)`, or `hash(cacheKeyPrefix + ":" + targetingKey)` when a `cacheKeyPrefix` is configured +- a `cacheKeyHash` derived from the provider's bound `domain` (if any) and the `targetingKey`: `hash(domain + ":" + targetingKey)`, additionally prefixed with `cacheKeyPrefix` when one is configured (`hash(cacheKeyPrefix + ":" + domain + ":" + targetingKey)`) - the time the entry was written, which can be used for diagnostics and optional implementation-specific staleness policies -Providers should support an optional `cacheKeyPrefix` configuration option. When provided, the prefix is included in the cache key hash: `hash(cacheKeyPrefix + ":" + targetingKey)`. This prevents collisions when multiple OFREP provider instances share the same local storage partition (e.g., two providers on the same web origin pointing at different OFREP servers). The prefix value is left to the application author; it could be the OFREP base URL, a project or auth token, or any other distinguishing string. When no prefix is configured, the cache key defaults to `hash(targetingKey)`. +The provider's bound `domain` is included in the cache key by default. In OpenFeature a provider is registered against a `domain` (the binding name passed to `setProvider`), which is the model's intended unit of isolation between provider instances. Including the `domain` in the cache key means two providers bound to different domains on the same storage partition do not collide on cache entries automatically, without any application configuration. Today providers are not handed their `domain` at initialization; [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) proposes supplying it to the `initialize` function so providers can use it here. When a provider has no bound `domain` (e.g. the default provider), the `domain` segment is empty. + +Providers should additionally support an optional `cacheKeyPrefix` configuration option. When provided, the prefix is included in the cache key hash: `hash(cacheKeyPrefix + ":" + domain + ":" + targetingKey)`. This supplements the `domain` for cases the `domain` alone does not separate, such as two providers bound to the same `domain` but pointing at different OFREP servers, or namespacing across storage partitions an application controls directly. The prefix value is left to the application author; it could be the OFREP base URL, a project or auth token, or any other distinguishing string. Example persisted value: ```json { "version": 1, - "cacheKeyHash": "hash(targetingKey)", + "cacheKeyHash": "hash(domain + ':' + targetingKey)", "etag": "\"abc123\"", "writtenAt": "2026-03-07T18:20:00Z", "data": { @@ -196,12 +200,12 @@ If the background refresh fails and the provider cannot confirm that cached valu ### Cache matching and fallback Providers should only reuse a persisted evaluation when it matches the current static-context inputs. -This includes a matching `cacheKeyHash` equal to `hash(targetingKey)`, or `hash(cacheKeyPrefix + ":" + targetingKey)` when a `cacheKeyPrefix` is configured. +This includes a matching `cacheKeyHash` derived from the provider's bound `domain` (if any) and the `targetingKey`, optionally prefixed with `cacheKeyPrefix`. -The cache key is intentionally derived from `targetingKey` alone rather than the full evaluation context. +The cache key is intentionally derived from the bound `domain` and `targetingKey` rather than the full evaluation context. Static-context evaluations on the server can depend on context properties beyond `targetingKey`, so cached values may not reflect the current full context. However, hashing the full context is impractical for local-cache-first startup because many implementations set volatile context properties on initialization (e.g. `lastSessionTime`, `lastSeen`, `sessionId`) that would change the hash on every app restart, defeating the purpose of persistence. -The accepted tradeoff is that the cache is keyed by stable user identity: a change in `targetingKey` (user switch, logout) invalidates the cache, but changes to other context properties do not. +The accepted tradeoff is that the cache is keyed by stable inputs (the bound `domain` and `targetingKey`): a change in `targetingKey` (user switch, logout) or in the bound `domain` invalidates the cache, but changes to other context properties do not. Those properties only affect evaluation when the server is reachable, at which point the provider refreshes anyway. When the provider has not initialized from cache (cache miss path, or `network-first` mode), providers must not silently fall back to persisted data for authorization failures, invalid requests, or other responses that indicate a configuration or protocol problem. In `network-first` mode this applies even when a matching persisted entry exists: the application has explicitly chosen to block on a fresh evaluation, and an auth or configuration error should be surfaced rather than masked by the cache. @@ -282,7 +286,7 @@ A single default (local-cache-first) with an explicit per-application opt-out is - Providers should avoid persisting raw `targetingKey` values when `cacheKeyHash` is sufficient for matching - Providers should expose a `cacheMode` option with values `local-cache-first` (default), `network-first`, and `disabled`. `network-first` and `disabled` block `initialize()` on the network request; `local-cache-first` returns from `initialize()` immediately when a persisted entry exists - Providers should expose an optional `cacheKeyPrefix` configuration option so multiple provider instances sharing one storage partition do not collide on the same storage key -- Providers should clear or replace persisted entries when the `targetingKey` changes, such as on logout or user switch +- Providers should clear or replace persisted entries when the cache key changes, such as on logout or user switch (`targetingKey` change) or when the provider is re-bound to a different `domain` - In `local-cache-first` mode, the `initialize()` function should return immediately when a matching cached entry exists, allowing the SDK to emit `PROVIDER_READY` from cache - Providers should emit `PROVIDER_CONFIGURATION_CHANGED` when fresh values replace cached values after a background refresh - If `onContextChanged()` is called while a background refresh is still in-flight, the provider should cancel or discard the in-flight request. The context-change evaluation supersedes it and should be the authoritative write to the persisted entry @@ -296,4 +300,7 @@ A single default (local-cache-first) with an explicit per-application opt-out is 1. Should providers support caching evaluations for multiple targeting keys (like LaunchDarkly's `maxCachedContexts`), or only retain the most recent? Multi-context caching enables instant user switching on shared devices but increases storage usage. 2. Should the storage key include a namespace to prevent collisions when multiple OFREP providers share the same local storage origin? - - **Answer:** Yes. Providers should support an optional `cacheKeyPrefix` configuration option. When provided, the cache key becomes `hash(cacheKeyPrefix + ":" + targetingKey)` instead of `hash(targetingKey)`. The prefix value is left to the application author (e.g., the OFREP base URL, a project or auth token, or any other distinguishing string). The default (no prefix) keeps the single-provider case simple. See the `cacheKeyPrefix` section in the Decision above. + - **Answer (amended 2026-06-19):** Yes, and by default. The provider's bound `domain` is now included in the cache key (`hash(domain + ":" + targetingKey)`), so two providers bound to different domains on the same storage partition do not collide without any application configuration. This relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying the bound `domain` to the provider's `initialize` function. The optional `cacheKeyPrefix` remains as a supplement for cases the `domain` alone does not separate (e.g. two providers on the same `domain` pointing at different OFREP servers). The original answer keyed only on `cacheKeyPrefix + targetingKey`; the amendment makes `domain` the automatic default and demotes `cacheKeyPrefix` to a supplement. See the `cacheKeyPrefix` section in the Decision above. +3. **(Soft proposal, 2026-06-19)** Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? + - **OFREP URL (low risk):** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. + - **Auth header (higher risk, deliberately soft):** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation and defeat persistence. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). If auth is keyed at all, it should be a stable credential identifier (e.g. a client/SDK key) rather than a rotating token, and likely opt-in rather than default. Recommend treating the URL as the primary resource binding and leaving auth-based keying as opt-in. From 8e0fcfe3ec0462a98691982d7f8435a970f8d74a Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:33:21 -0400 Subject: [PATCH 2/9] docs: keep original cacheKeyPrefix answer, add domain as amendment Signed-off-by: Jonathan Norris --- .../adrs/0009-local-storage-for-static-context-providers.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index 667621f..d2d6925 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -300,7 +300,8 @@ A single default (local-cache-first) with an explicit per-application opt-out is 1. Should providers support caching evaluations for multiple targeting keys (like LaunchDarkly's `maxCachedContexts`), or only retain the most recent? Multi-context caching enables instant user switching on shared devices but increases storage usage. 2. Should the storage key include a namespace to prevent collisions when multiple OFREP providers share the same local storage origin? - - **Answer (amended 2026-06-19):** Yes, and by default. The provider's bound `domain` is now included in the cache key (`hash(domain + ":" + targetingKey)`), so two providers bound to different domains on the same storage partition do not collide without any application configuration. This relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying the bound `domain` to the provider's `initialize` function. The optional `cacheKeyPrefix` remains as a supplement for cases the `domain` alone does not separate (e.g. two providers on the same `domain` pointing at different OFREP servers). The original answer keyed only on `cacheKeyPrefix + targetingKey`; the amendment makes `domain` the automatic default and demotes `cacheKeyPrefix` to a supplement. See the `cacheKeyPrefix` section in the Decision above. + - **Answer:** Yes. Providers should support an optional `cacheKeyPrefix` configuration option. When provided, the cache key becomes `hash(cacheKeyPrefix + ":" + targetingKey)` instead of `hash(targetingKey)`. The prefix value is left to the application author (e.g., the OFREP base URL, a project or auth token, or any other distinguishing string). The default (no prefix) keeps the single-provider case simple. See the `cacheKeyPrefix` section in the Decision above. + - **Amendment (2026-06-19):** The provider's bound `domain` is now included in the cache key by default (`hash(domain + ":" + targetingKey)`), so two providers bound to different domains on the same storage partition do not collide without any application configuration. This relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying the bound `domain` to the provider's `initialize` function. The optional `cacheKeyPrefix` above remains as a supplement for cases the `domain` alone does not separate (e.g. two providers on the same `domain` pointing at different OFREP servers). 3. **(Soft proposal, 2026-06-19)** Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? - **OFREP URL (low risk):** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. - **Auth header (higher risk, deliberately soft):** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation and defeat persistence. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). If auth is keyed at all, it should be a stable credential identifier (e.g. a client/SDK key) rather than a rotating token, and likely opt-in rather than default. Recommend treating the URL as the primary resource binding and leaving auth-based keying as opt-in. From a482a6864dfa892bda0ec80ba1b9ee300936a9a2 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:42:44 -0400 Subject: [PATCH 3/9] docs: include OFREP url and auth in cache key alongside domain Signed-off-by: Jonathan Norris --- ...al-storage-for-static-context-providers.md | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index d2d6925..c442a8a 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -6,7 +6,7 @@ Date: 2026-03-06 Accepted -Proposed amendment (2026-06-19): include the provider's bound `domain` in the cache key, and soft-propose tying the cache key to the OFREP resource (URL, and optionally auth) it fetches from. See [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). +Proposed amendment (2026-06-19): tie the cache key to the OFREP resource the evaluation was fetched from by including the provider's bound `domain`, the OFREP base URL, and the auth credential, in addition to the `targetingKey`. Including the auth credential is the most open to discussion (see Open Question #3). See [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). ## Context @@ -36,19 +36,24 @@ The persisted entry should include: - the bulk evaluation payload - the associated `ETag`, if one was returned -- a `cacheKeyHash` derived from the provider's bound `domain` (if any) and the `targetingKey`: `hash(domain + ":" + targetingKey)`, additionally prefixed with `cacheKeyPrefix` when one is configured (`hash(cacheKeyPrefix + ":" + domain + ":" + targetingKey)`) +- a `cacheKeyHash` derived from the OFREP resource the evaluation was fetched from and the `targetingKey`: the OFREP base URL, the auth credential, the provider's bound `domain` (if any), and the `targetingKey`, i.e. `hash(url + ":" + auth + ":" + domain + ":" + targetingKey)`, additionally prefixed with `cacheKeyPrefix` when one is configured (`hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`) - the time the entry was written, which can be used for diagnostics and optional implementation-specific staleness policies -The provider's bound `domain` is included in the cache key by default. In OpenFeature a provider is registered against a `domain` (the binding name passed to `setProvider`), which is the model's intended unit of isolation between provider instances. Including the `domain` in the cache key means two providers bound to different domains on the same storage partition do not collide on cache entries automatically, without any application configuration. Today providers are not handed their `domain` at initialization; [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) proposes supplying it to the `initialize` function so providers can use it here. When a provider has no bound `domain` (e.g. the default provider), the `domain` segment is empty. +The cache key is tied to the OFREP resource the evaluation was fetched from. The persisted evaluation is a function of the server it came from and the identity it was requested for, so the cache key includes: -Providers should additionally support an optional `cacheKeyPrefix` configuration option. When provided, the prefix is included in the cache key hash: `hash(cacheKeyPrefix + ":" + domain + ":" + targetingKey)`. This supplements the `domain` for cases the `domain` alone does not separate, such as two providers bound to the same `domain` but pointing at different OFREP servers, or namespacing across storage partitions an application controls directly. The prefix value is left to the application author; it could be the OFREP base URL, a project or auth token, or any other distinguishing string. +- the **OFREP base URL**, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and instances on the same storage partition pointing at different servers separate automatically. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. +- the **auth credential**, so evaluations fetched under different credentials (different projects, environments, or keys against the same URL) do not collide. Including the credential is the most open to discussion: OFREP supports rotating or short-lived tokens via `headersFactory`, and a token that rotates on every request would defeat persistence. In practice auth does not change on every request, so it still provides useful separation for the common case of a stable credential. See Open Question #3. +- the provider's bound **`domain`**. In OpenFeature a provider is registered against a `domain` (the binding name passed to `setProvider`), which is the model's intended unit of isolation between provider instances. Today providers are not handed their `domain` at initialization; [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) proposes supplying it to the `initialize` function so providers can use it here. When a provider has no bound `domain` (e.g. the default provider), the `domain` segment is empty. +- the **`targetingKey`**, keying the entry to the user identity (see "Cache matching and fallback" below). + +Providers should additionally support an optional `cacheKeyPrefix` configuration option. When provided, the prefix is included in the cache key hash: `hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`. This lets applications namespace across storage partitions they control directly, on top of the automatic resource binding. The prefix value is left to the application author and can be any distinguishing string. Example persisted value: ```json { "version": 1, - "cacheKeyHash": "hash(domain + ':' + targetingKey)", + "cacheKeyHash": "hash(url + ':' + auth + ':' + domain + ':' + targetingKey)", "etag": "\"abc123\"", "writtenAt": "2026-03-07T18:20:00Z", "data": { @@ -200,12 +205,12 @@ If the background refresh fails and the provider cannot confirm that cached valu ### Cache matching and fallback Providers should only reuse a persisted evaluation when it matches the current static-context inputs. -This includes a matching `cacheKeyHash` derived from the provider's bound `domain` (if any) and the `targetingKey`, optionally prefixed with `cacheKeyPrefix`. +This includes a matching `cacheKeyHash` derived from the OFREP resource (base URL, auth credential, and bound `domain`) and the `targetingKey`, optionally prefixed with `cacheKeyPrefix`. -The cache key is intentionally derived from the bound `domain` and `targetingKey` rather than the full evaluation context. +The cache key is intentionally derived from the OFREP resource and `targetingKey` rather than the full evaluation context. Static-context evaluations on the server can depend on context properties beyond `targetingKey`, so cached values may not reflect the current full context. However, hashing the full context is impractical for local-cache-first startup because many implementations set volatile context properties on initialization (e.g. `lastSessionTime`, `lastSeen`, `sessionId`) that would change the hash on every app restart, defeating the purpose of persistence. -The accepted tradeoff is that the cache is keyed by stable inputs (the bound `domain` and `targetingKey`): a change in `targetingKey` (user switch, logout) or in the bound `domain` invalidates the cache, but changes to other context properties do not. +The accepted tradeoff is that the cache is keyed by stable inputs (the OFREP base URL, the auth credential, the bound `domain`, and the `targetingKey`): a change in `targetingKey` (user switch, logout), in the bound `domain`, or in the resource the provider points at invalidates the cache, but changes to other context properties do not. Those properties only affect evaluation when the server is reachable, at which point the provider refreshes anyway. When the provider has not initialized from cache (cache miss path, or `network-first` mode), providers must not silently fall back to persisted data for authorization failures, invalid requests, or other responses that indicate a configuration or protocol problem. In `network-first` mode this applies even when a matching persisted entry exists: the application has explicitly chosen to block on a fresh evaluation, and an auth or configuration error should be surfaced rather than masked by the cache. @@ -301,7 +306,8 @@ A single default (local-cache-first) with an explicit per-application opt-out is 1. Should providers support caching evaluations for multiple targeting keys (like LaunchDarkly's `maxCachedContexts`), or only retain the most recent? Multi-context caching enables instant user switching on shared devices but increases storage usage. 2. Should the storage key include a namespace to prevent collisions when multiple OFREP providers share the same local storage origin? - **Answer:** Yes. Providers should support an optional `cacheKeyPrefix` configuration option. When provided, the cache key becomes `hash(cacheKeyPrefix + ":" + targetingKey)` instead of `hash(targetingKey)`. The prefix value is left to the application author (e.g., the OFREP base URL, a project or auth token, or any other distinguishing string). The default (no prefix) keeps the single-provider case simple. See the `cacheKeyPrefix` section in the Decision above. - - **Amendment (2026-06-19):** The provider's bound `domain` is now included in the cache key by default (`hash(domain + ":" + targetingKey)`), so two providers bound to different domains on the same storage partition do not collide without any application configuration. This relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying the bound `domain` to the provider's `initialize` function. The optional `cacheKeyPrefix` above remains as a supplement for cases the `domain` alone does not separate (e.g. two providers on the same `domain` pointing at different OFREP servers). -3. **(Soft proposal, 2026-06-19)** Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? + - **Amendment (2026-06-19):** Collision avoidance no longer depends on an application-supplied prefix. The cache key now ties to the OFREP resource and identity by default (`hash(url + ":" + auth + ":" + domain + ":" + targetingKey)`), so providers pointing at different servers, using different credentials, or bound to different domains on the same storage partition do not collide without any application configuration. The bound `domain` relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying it to the provider's `initialize` function. The optional `cacheKeyPrefix` above remains as a supplement for namespacing across storage partitions an application controls directly. See Open Question #3 for the resource-binding rationale. +3. **(Amendment, 2026-06-19)** Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? - **OFREP URL (low risk):** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. - - **Auth header (higher risk, deliberately soft):** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation and defeat persistence. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). If auth is keyed at all, it should be a stable credential identifier (e.g. a client/SDK key) rather than a rotating token, and likely opt-in rather than default. Recommend treating the URL as the primary resource binding and leaving auth-based keying as opt-in. + - **Auth header (higher risk, deliberately soft):** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). + - **Proposed (2026-06-19):** Include all three (OFREP base URL, auth credential, and bound `domain`) in the cache key; the Decision section above reflects this. The auth credential is the most open to discussion: it is less effective for short-lived or rotating tokens, but auth does not change on every request, so it still provides useful separation for the common case of a stable credential. From 9fae38a83577a0c2de953b7e35498681ec8a14df Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:50:03 -0400 Subject: [PATCH 4/9] docs: tighten cache key resource section in ADR 0009 Signed-off-by: Jonathan Norris --- .../0009-local-storage-for-static-context-providers.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index c442a8a..5f7d1bb 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -39,14 +39,14 @@ The persisted entry should include: - a `cacheKeyHash` derived from the OFREP resource the evaluation was fetched from and the `targetingKey`: the OFREP base URL, the auth credential, the provider's bound `domain` (if any), and the `targetingKey`, i.e. `hash(url + ":" + auth + ":" + domain + ":" + targetingKey)`, additionally prefixed with `cacheKeyPrefix` when one is configured (`hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`) - the time the entry was written, which can be used for diagnostics and optional implementation-specific staleness policies -The cache key is tied to the OFREP resource the evaluation was fetched from. The persisted evaluation is a function of the server it came from and the identity it was requested for, so the cache key includes: +The cache key is tied to the OFREP resource the evaluation was fetched from and the identity it was requested for, so it includes: -- the **OFREP base URL**, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and instances on the same storage partition pointing at different servers separate automatically. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. -- the **auth credential**, so evaluations fetched under different credentials (different projects, environments, or keys against the same URL) do not collide. Including the credential is the most open to discussion: OFREP supports rotating or short-lived tokens via `headersFactory`, and a token that rotates on every request would defeat persistence. In practice auth does not change on every request, so it still provides useful separation for the common case of a stable credential. See Open Question #3. -- the provider's bound **`domain`**. In OpenFeature a provider is registered against a `domain` (the binding name passed to `setProvider`), which is the model's intended unit of isolation between provider instances. Today providers are not handed their `domain` at initialization; [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) proposes supplying it to the `initialize` function so providers can use it here. When a provider has no bound `domain` (e.g. the default provider), the `domain` segment is empty. +- the **OFREP base URL**, so a provider pointed at a different server does not serve another server's cached evaluations. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. +- the **auth credential**, so evaluations fetched under different credentials (projects, environments, or keys against the same URL) do not collide. This is the most open to discussion: rotating or short-lived tokens (via `headersFactory`) would change the hash and defeat persistence, but a stable credential gives useful separation for the common case. See Open Question #3. +- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`). [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) proposes supplying it to `initialize` so providers can use it here; it is empty when a provider has no bound `domain` (e.g. the default provider). - the **`targetingKey`**, keying the entry to the user identity (see "Cache matching and fallback" below). -Providers should additionally support an optional `cacheKeyPrefix` configuration option. When provided, the prefix is included in the cache key hash: `hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`. This lets applications namespace across storage partitions they control directly, on top of the automatic resource binding. The prefix value is left to the application author and can be any distinguishing string. +Providers should additionally support an optional `cacheKeyPrefix` option, prepended to the hash (`hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`) so applications can namespace across storage partitions they control directly. The prefix can be any distinguishing string. Example persisted value: From 64645530b969bb4a1aa54df1aa99c424c8275511 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:51:59 -0400 Subject: [PATCH 5/9] docs: assume spec#393 lands for domain in initialize Signed-off-by: Jonathan Norris --- .../adrs/0009-local-storage-for-static-context-providers.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index 5f7d1bb..bb0154e 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -41,9 +41,9 @@ The persisted entry should include: The cache key is tied to the OFREP resource the evaluation was fetched from and the identity it was requested for, so it includes: -- the **OFREP base URL**, so a provider pointed at a different server does not serve another server's cached evaluations. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. -- the **auth credential**, so evaluations fetched under different credentials (projects, environments, or keys against the same URL) do not collide. This is the most open to discussion: rotating or short-lived tokens (via `headersFactory`) would change the hash and defeat persistence, but a stable credential gives useful separation for the common case. See Open Question #3. -- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`). [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) proposes supplying it to `initialize` so providers can use it here; it is empty when a provider has no bound `domain` (e.g. the default provider). +- the **OFREP base URL**, so a provider pointed at a different server does not serve another server's cached evaluations. +- the **auth credential**, so evaluations fetched under different credentials (projects, environments, or keys against the same URL) do not collide. +- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`), supplied to `initialize` per [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). It is empty when a provider has no bound `domain` (e.g. the default provider). - the **`targetingKey`**, keying the entry to the user identity (see "Cache matching and fallback" below). Providers should additionally support an optional `cacheKeyPrefix` option, prepended to the hash (`hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`) so applications can namespace across storage partitions they control directly. The prefix can be any distinguishing string. From 96d4da267c7949c04354095923d08012397c516b Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:55:01 -0400 Subject: [PATCH 6/9] docs: propose removing cacheKeyPrefix as open question Signed-off-by: Jonathan Norris --- .../adrs/0009-local-storage-for-static-context-providers.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index bb0154e..c2f2a8d 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -43,7 +43,7 @@ The cache key is tied to the OFREP resource the evaluation was fetched from and - the **OFREP base URL**, so a provider pointed at a different server does not serve another server's cached evaluations. - the **auth credential**, so evaluations fetched under different credentials (projects, environments, or keys against the same URL) do not collide. -- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`), supplied to `initialize` per [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). It is empty when a provider has no bound `domain` (e.g. the default provider). +- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`), supplied to `initialize` per [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). It is empty when a provider has no bound `domain`. - the **`targetingKey`**, keying the entry to the user identity (see "Cache matching and fallback" below). Providers should additionally support an optional `cacheKeyPrefix` option, prepended to the hash (`hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`) so applications can namespace across storage partitions they control directly. The prefix can be any distinguishing string. @@ -311,3 +311,5 @@ A single default (local-cache-first) with an explicit per-application opt-out is - **OFREP URL (low risk):** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. - **Auth header (higher risk, deliberately soft):** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). - **Proposed (2026-06-19):** Include all three (OFREP base URL, auth credential, and bound `domain`) in the cache key; the Decision section above reflects this. The auth credential is the most open to discussion: it is less effective for short-lived or rotating tokens, but auth does not change on every request, so it still provides useful separation for the common case of a stable credential. +4. **(Amendment, 2026-06-19)** Should the optional `cacheKeyPrefix` configuration option be removed entirely? + - With the OFREP base URL, auth credential, and bound `domain` now part of the cache key by default, the cases `cacheKeyPrefix` was introduced to solve (collisions between provider instances sharing a storage partition) are already handled automatically. The only remaining use is namespacing across storage partitions an application controls directly, which is arguably the application's responsibility rather than the provider's. Removing it would simplify the configuration surface. The counter-argument is that it remains a cheap, explicit escape hatch for cases the resource binding does not anticipate. **Proposed:** lean toward removing it, open to keeping it as an escape hatch. From ab411b243747b7836b0f8551c5c5ee5c6c35feeb Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 10:57:53 -0400 Subject: [PATCH 7/9] docs: simplify url and auth bullet labels in open question 3 Signed-off-by: Jonathan Norris --- .../adrs/0009-local-storage-for-static-context-providers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index c2f2a8d..2a1a902 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -308,8 +308,8 @@ A single default (local-cache-first) with an explicit per-application opt-out is - **Answer:** Yes. Providers should support an optional `cacheKeyPrefix` configuration option. When provided, the cache key becomes `hash(cacheKeyPrefix + ":" + targetingKey)` instead of `hash(targetingKey)`. The prefix value is left to the application author (e.g., the OFREP base URL, a project or auth token, or any other distinguishing string). The default (no prefix) keeps the single-provider case simple. See the `cacheKeyPrefix` section in the Decision above. - **Amendment (2026-06-19):** Collision avoidance no longer depends on an application-supplied prefix. The cache key now ties to the OFREP resource and identity by default (`hash(url + ":" + auth + ":" + domain + ":" + targetingKey)`), so providers pointing at different servers, using different credentials, or bound to different domains on the same storage partition do not collide without any application configuration. The bound `domain` relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying it to the provider's `initialize` function. The optional `cacheKeyPrefix` above remains as a supplement for namespacing across storage partitions an application controls directly. See Open Question #3 for the resource-binding rationale. 3. **(Amendment, 2026-06-19)** Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? - - **OFREP URL (low risk):** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. - - **Auth header (higher risk, deliberately soft):** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). + - **OFREP URL** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. + - **Auth header:** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). - **Proposed (2026-06-19):** Include all three (OFREP base URL, auth credential, and bound `domain`) in the cache key; the Decision section above reflects this. The auth credential is the most open to discussion: it is less effective for short-lived or rotating tokens, but auth does not change on every request, so it still provides useful separation for the common case of a stable credential. 4. **(Amendment, 2026-06-19)** Should the optional `cacheKeyPrefix` configuration option be removed entirely? - With the OFREP base URL, auth credential, and bound `domain` now part of the cache key by default, the cases `cacheKeyPrefix` was introduced to solve (collisions between provider instances sharing a storage partition) are already handled automatically. The only remaining use is namespacing across storage partitions an application controls directly, which is arguably the application's responsibility rather than the provider's. Removing it would simplify the configuration surface. The counter-argument is that it remains a cheap, explicit escape hatch for cases the resource binding does not anticipate. **Proposed:** lean toward removing it, open to keeping it as an escape hatch. From 46615d6f7eda58e56c94d088d6cd742ac1176f4a Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Fri, 19 Jun 2026 13:54:57 -0400 Subject: [PATCH 8/9] docs: reference domain-scoped provider declaration for unambiguous cache key Signed-off-by: Jonathan Norris --- .../adrs/0009-local-storage-for-static-context-providers.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index 2a1a902..469dd41 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -43,7 +43,7 @@ The cache key is tied to the OFREP resource the evaluation was fetched from and - the **OFREP base URL**, so a provider pointed at a different server does not serve another server's cached evaluations. - the **auth credential**, so evaluations fetched under different credentials (projects, environments, or keys against the same URL) do not collide. -- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`), supplied to `initialize` per [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). It is empty when a provider has no bound `domain`. +- the provider's bound **`domain`**, OpenFeature's intended unit of isolation between provider instances (the binding name passed to `setProvider`), supplied to `initialize` per [open-feature/spec#393](https://github.com/open-feature/spec/pull/393). A persisting OFREP provider should declare itself `domain-scoped` (also spec#393) so the API binds it to at most one `domain`, making the `domain` it keys on unambiguous. It is empty when a provider has no bound `domain`. - the **`targetingKey`**, keying the entry to the user identity (see "Cache matching and fallback" below). Providers should additionally support an optional `cacheKeyPrefix` option, prepended to the hash (`hash(cacheKeyPrefix + ":" + url + ":" + auth + ":" + domain + ":" + targetingKey)`) so applications can namespace across storage partitions they control directly. The prefix can be any distinguishing string. @@ -288,6 +288,7 @@ A single default (local-cache-first) with an explicit per-application opt-out is - "Local storage" means a local persistent key-value store appropriate for the runtime, such as browser `localStorage` on the web or an equivalent mobile storage mechanism - Providers should version their persisted format so future schema changes can be handled safely +- Persisting providers should declare themselves `domain-scoped` (per [open-feature/spec#393](https://github.com/open-feature/spec/pull/393)) so the API binds each instance to at most one `domain`. This keeps the `domain` component of the cache key unambiguous and avoids a single shared instance writing entries for more than one `domain` - Providers should avoid persisting raw `targetingKey` values when `cacheKeyHash` is sufficient for matching - Providers should expose a `cacheMode` option with values `local-cache-first` (default), `network-first`, and `disabled`. `network-first` and `disabled` block `initialize()` on the network request; `local-cache-first` returns from `initialize()` immediately when a persisted entry exists - Providers should expose an optional `cacheKeyPrefix` configuration option so multiple provider instances sharing one storage partition do not collide on the same storage key From a871ed24c2729ee7dc6f205c8a238d9a988ee0b1 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Thu, 25 Jun 2026 15:44:14 -0400 Subject: [PATCH 9/9] docs: consolidate amendment dates to top of ADR 0009 Signed-off-by: Jonathan Norris --- .../0009-local-storage-for-static-context-providers.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service/adrs/0009-local-storage-for-static-context-providers.md b/service/adrs/0009-local-storage-for-static-context-providers.md index 469dd41..a2f2950 100644 --- a/service/adrs/0009-local-storage-for-static-context-providers.md +++ b/service/adrs/0009-local-storage-for-static-context-providers.md @@ -299,7 +299,7 @@ A single default (local-cache-first) with an explicit per-application opt-out is - On the first cold start in `local-cache-first` mode (no persisted entry), `initialize()` blocks on the network request as normal. Local-cache-first initialization only returns immediately once a successful evaluation has been persisted - In `network-first` mode, applications should consider lowering the provider's request timeout (e.g., `timeoutMs`) from the default so that initialization falls back to cache or fails quickly when the network is unavailable, rather than leaving users on a loading state for the full timeout - SDK documentation should note that initial evaluations may return cached values (with `CACHED` reason) that are subsequently updated when fresh values arrive -- Providers should enforce a configurable TTL on persisted entries to ensure stale caches are eventually purged, particularly in cases where the provider can no longer refresh from the server (e.g. persistent auth errors). Since auth and config errors do not clear the persisted cache, the TTL is the mechanism that prevents indefinitely stale data. A persisted entry past its TTL must not be served to the application: the provider should treat it as a cache miss and fall through to the cache-miss path. DevCycle uses a 30-day default (`configCacheTTL`) as a reference. +- Providers should enforce a configurable TTL on persisted entries to ensure stale caches are eventually purged, particularly in cases where the provider can no longer refresh from the server (e.g. persistent auth errors). Since auth and config errors do not clear the persisted cache, the TTL is the mechanism that prevents indefinitely stale data. A persisted entry past its TTL must not be served to the application: the provider should treat it as a cache miss and fall through to the cache-miss path. DevCycle uses a 30-day default (`configCacheTTL`) as a ref erence. - If a storage write fails (e.g. quota exceeded, permission denied), the provider should log the error and continue operating with the fresh values in memory. The previously persisted entry, if any, remains on disk for the next cold start. ## Open Questions @@ -307,10 +307,10 @@ A single default (local-cache-first) with an explicit per-application opt-out is 1. Should providers support caching evaluations for multiple targeting keys (like LaunchDarkly's `maxCachedContexts`), or only retain the most recent? Multi-context caching enables instant user switching on shared devices but increases storage usage. 2. Should the storage key include a namespace to prevent collisions when multiple OFREP providers share the same local storage origin? - **Answer:** Yes. Providers should support an optional `cacheKeyPrefix` configuration option. When provided, the cache key becomes `hash(cacheKeyPrefix + ":" + targetingKey)` instead of `hash(targetingKey)`. The prefix value is left to the application author (e.g., the OFREP base URL, a project or auth token, or any other distinguishing string). The default (no prefix) keeps the single-provider case simple. See the `cacheKeyPrefix` section in the Decision above. - - **Amendment (2026-06-19):** Collision avoidance no longer depends on an application-supplied prefix. The cache key now ties to the OFREP resource and identity by default (`hash(url + ":" + auth + ":" + domain + ":" + targetingKey)`), so providers pointing at different servers, using different credentials, or bound to different domains on the same storage partition do not collide without any application configuration. The bound `domain` relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying it to the provider's `initialize` function. The optional `cacheKeyPrefix` above remains as a supplement for namespacing across storage partitions an application controls directly. See Open Question #3 for the resource-binding rationale. -3. **(Amendment, 2026-06-19)** Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? + - **Amendment:** Collision avoidance no longer depends on an application-supplied prefix. The cache key now ties to the OFREP resource and identity by default (`hash(url + ":" + auth + ":" + domain + ":" + targetingKey)`), so providers pointing at different servers, using different credentials, or bound to different domains on the same storage partition do not collide without any application configuration. The bound `domain` relies on [open-feature/spec#393](https://github.com/open-feature/spec/pull/393) supplying it to the provider's `initialize` function. The optional `cacheKeyPrefix` above remains as a supplement for namespacing across storage partitions an application controls directly. See Open Question #3 for the resource-binding rationale. +3. Should the cache key also be tied to the OFREP resource the evaluation was fetched from, rather than relying on the application to supply a distinguishing `cacheKeyPrefix`? - **OFREP URL** Folding the OFREP base URL into the cache key (e.g. `hash(url + ":" + domain + ":" + targetingKey)`) ties cached results directly to the resource that produced them, so a provider reconfigured to point at a different server does not serve another server's cached evaluations, and same-origin instances pointing at different servers separate automatically without an explicitly configured prefix. The base URL is stable across restarts, so it does not reintroduce the volatile-input problem. This mirrors vendor SDKs that key their persisted cache by SDK key or environment (Statsig, Eppo, ConfigCat). The cost is that changing the configured URL silently invalidates the cache, which is usually the desired behavior. - **Auth header:** Including the auth credential would tie the cache even more tightly to the protected resource, but credentials are not always stable. OFREP supports rotating/short-lived tokens via `headersFactory`, and a rotating bearer token would change the hash on every rotation. This is the same reason the original ADR dropped `authToken` from the cache key (see the [protocol#64](https://github.com/open-feature/protocol/pull/64) discussion). - - **Proposed (2026-06-19):** Include all three (OFREP base URL, auth credential, and bound `domain`) in the cache key; the Decision section above reflects this. The auth credential is the most open to discussion: it is less effective for short-lived or rotating tokens, but auth does not change on every request, so it still provides useful separation for the common case of a stable credential. -4. **(Amendment, 2026-06-19)** Should the optional `cacheKeyPrefix` configuration option be removed entirely? + - **Proposed:** Include all three (OFREP base URL, auth credential, and bound `domain`) in the cache key; the Decision section above reflects this. The auth credential is the most open to discussion: it is less effective for short-lived or rotating tokens, but auth does not change on every request, so it still provides useful separation for the common case of a stable credential. +4. Should the optional `cacheKeyPrefix` configuration option be removed entirely? - With the OFREP base URL, auth credential, and bound `domain` now part of the cache key by default, the cases `cacheKeyPrefix` was introduced to solve (collisions between provider instances sharing a storage partition) are already handled automatically. The only remaining use is namespacing across storage partitions an application controls directly, which is arguably the application's responsibility rather than the provider's. Removing it would simplify the configuration surface. The counter-argument is that it remains a cheap, explicit escape hatch for cases the resource binding does not anticipate. **Proposed:** lean toward removing it, open to keeping it as an escape hatch.