Skip to content

feat: supply the bound domain to provider initialization#393

Open
jonathannorris wants to merge 10 commits into
mainfrom
feat/provider-init-domain
Open

feat: supply the bound domain to provider initialization#393
jonathannorris wants to merge 10 commits into
mainfrom
feat/provider-init-domain

Conversation

@jonathannorris

@jonathannorris jonathannorris commented Jun 18, 2026

Copy link
Copy Markdown
Member

Amends provider initialization so the domain a provider is bound to is supplied to its initialize function, letting providers scope domain-specific behavior (such as partitioning a persistent cache) to that domain.

Motivation

Provider-level persistent caches (e.g. the OFREP web provider's local persistence) currently have no way to know which domain they're bound to, so two providers on the same storage partition can collide on cache entries even though OpenFeature's domain model is supposed to keep them isolated. See slack thread, open-feature/js-sdk-contrib#1566, and ADR 0009.

A follow-up OFREP ADR will add the domain to the cache key.

Changes

Two parts, both kept language-agnostic so implementations can use overloads, optional arguments, etc.:

Supply the bound domain to init (folded into the existing init requirements, per earlier review feedback):

  • 2.4.1 (provider): the initialize function accepts the global evaluation context and the bound domain (if any).
  • 1.1.2.2 (API): the provider mutator MUST supply the bound domain (if any) when invoking initialize.

Let a provider declare it's domain-scoped, enforced by the API (new requirements):

  • 2.4.3 (provider): a provider MAY declare itself domain-scoped when it holds per-domain state such as a persistent cache.
  • 1.1.8 (condition) / 1.1.8.1 (API): when a provider is domain-scoped, the provider mutator MUST NOT bind it to more than one domain, and rejects any attempt to do so.

Resolved

The earlier open question (a provider bound to multiple domains is initialized once and receives only its first binding's domain) is now addressed for stateful providers: declaring domain-scoped restricts the instance to a single domain, so the domain it keys on is unambiguous. Stateless providers are unaffected and can still back multiple domains.

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f60595a8-51b1-4741-83eb-402741649213

📥 Commits

Reviewing files that changed from the base of the PR and between 502c325 and 1727950.

📒 Files selected for processing (2)
  • specification.json
  • specification/sections/02-providers.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • specification.json
  • specification/sections/02-providers.md

📝 Walkthrough

Walkthrough

The specification is extended to add domain parameter passing to provider initialization, define domain-scoped provider semantics, and constrain domain-scoped providers to single-domain binding. The provider initialize function now accepts both the evaluation context and nullable bound domain. The provider mutator requirement is updated to invoke initialize with the bound domain when registering a new provider. A new domain-scoped provider declaration and corresponding binding constraints are introduced to prevent the same domain-scoped provider instance from being bound to multiple domains. Both Markdown source files and specification.json are updated.

Changes

Domain scoping for provider initialization and binding

Layer / File(s) Summary
Provider initialize signature update
specification/sections/02-providers.md, specification.json
Requirement 2.4.1 and its JSON counterpart are updated so initialize accepts a nullable domain argument alongside initialContext, with documentation on one-time initialization semantics for multi-domain binding and default-provider behavior when unbound.
Provider mutator domain-passing requirement
specification/sections/01-flag-evaluation.md, specification.json
Requirement 1.1.2.2 and its JSON counterpart are updated to require that the provider mutator calls initialize with the bound domain before using a newly registered provider to resolve flag values.
Domain-scoped provider declaration
specification/sections/02-providers.md, specification.json
Requirements 2.4.3 and 2.4.4 are added defining the optional domain-scoped declaration for providers maintaining domain-specific state, specifying that such providers must accept the bound domain during initialization and bind to at most one domain for unambiguous state isolation.
Domain-scoped provider binding constraints
specification/sections/01-flag-evaluation.md, specification.json
Conditional Requirement 1.1.8.1 is added specifying that domain-scoped provider instances cannot bind to multiple domains, that per-domain state is keyed to the domain passed into initialize, and that additional-binding attempts must be rejected. Requirement 1.8.4 is updated to clarify that only non-domain-scoped providers may be registered to multiple domains within a single API instance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: amending provider initialization to supply the bound domain parameter.
Description check ✅ Passed The description comprehensively explains the motivation, changes, and resolution, directly relating to the specification updates documented in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread specification/sections/02-providers.md Outdated
Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@MattIPv4

Copy link
Copy Markdown
Member

Thank you :) this looks good to me

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>

@lukas-reining lukas-reining left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!


```java
The `domain` the provider is registered under is also supplied, allowing the provider to scope domain-specific behavior, such as partitioning a persistent cache, so that multiple providers sharing the same storage do not collide.
A `provider` instance is initialized only once, even when bound to multiple `domains`; in that case the `domain` supplied is the one under which it was first registered.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit worried about this being confusing to new provider authors but I think it is worth it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could potentially have an optional domain updated function on the provider if we think its necessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is mostly documentation. Most providers can probably ignore it - but if you need to maintain some cached state, it's important use to use this. I think that's the simple doc line we can add - this is a param for provider-maintained namepacing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toddbaert agreed, I think we keep this as a MAY rather than required; it's a documentation/namespacing param most providers can safely ignore. And the single-domain binding for domain-scoped providers (1.1.8.1) removes the need for a separate domain-setter I floated earlier: the domain is fixed at init for the providers that actually care, so we avoid the races a separate setter would introduce.

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@jonathannorris jonathannorris marked this pull request as ready for review June 19, 2026 14:15
@jonathannorris jonathannorris requested a review from a team as a code owner June 19, 2026 14:15
Comment thread specification/sections/02-providers.md Outdated
#### Requirement 2.4.1

> The `provider` **MAY** define an initialization function which accepts the global `evaluation context` as an argument and performs initialization logic relevant to the provider.
> The `provider` **MAY** define an initialization function which accepts the global `evaluation context` and the bound `domain` (if any) as arguments and performs initialization logic relevant to the provider.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it optional to supply either, or if you supply one you must supply the other? it's not clear from the wording

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toddbaert @JamieSinn I think we just keep 2.4.1 language-agnostic and word it so the domain is clearly optional/additive, leaving each SDK to introduce it backwards-compatibly in its own idiom (optional arg, overload, separate interface, whatever fits).

The MAY already allows this; I'd just tweak the wording so it's obvious the domain is optional to consume and existing providers aren't forced into a breaking change. Worth noting this will still be a breaking change in Go (its Init(EvaluationContext) error is a required interface method with no overloads) and likely a few other languages, where a separate optional interface with a fallback would be needed instead.

The initialization function is an ideal place for such logic.

The `domain` the provider is registered under is also supplied, allowing the provider to scope domain-specific behavior, such as partitioning a persistent cache, so that multiple providers sharing the same storage do not collide.
A `provider` instance is initialized only once, even when bound to multiple `domains`; in that case the `domain` supplied is the one under which it was first registered.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this change, if by any chance somebody reassigns the first domain to a different provider? What is the expectation, should this change the behaviour? Theoretically, there could be a new provider with a different backend and still the same cache keys.

@toddbaert toddbaert Jun 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this change, if by any chance somebody reassigns the first domain to a different provider? What is the expectation, should this change the behaviour? Theoretically, there could be a new provider with a different backend and still the same cache keys.

Ya, that's a good point.

Similarly, you can, hypothetically, use the same provider instance in 2 domains. That also brings up a similar question (which domain assignment "wins"). I don't have great answers to these at the moment.

That said, I since domains are the fundamental mechanism of isolation, I think we need to supply this information to providers so they can properly scope their caches, etc.

An obvious alternative is to provide a domain setter, but if that's separated from the init, I think it brings up a bunch of possible races we don't want to contend with.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aepfli @toddbaert I think both of these are covered now:

  • Reassigning a domain to a different provider with a different backend: the OFREP cache key in the follow-up ADR (docs: amend ADR 0009 to tie cache key to OFREP resource (domain, url, auth) protocol#80) includes the OFREP URL and auth, so a different backend produces a different key even on the same domain. The new provider won't read the old one's cached entries.
  • Same instance bound to two domains ("which wins"): with the domain-scoped declaration (2.4.3) and 1.1.8.1, a provider that maintains per-domain state can only be bound to a single domain, and the API rejects a second binding. So there's no ambiguity for the providers that actually care. Stateless providers can still share across domains since they don't key anything on it.

…g enforcement

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@jonathannorris

Copy link
Copy Markdown
Member Author

Pushed an update adding a domain-scoped provider declaration, based on the #openfeature-js thread.

The point that a provider isn't 1:1 with a domain is fair, so rather than relying on providers to use the bound domain correctly, this makes it an enforced contract:

  • 2.4.3: a provider MAY declare itself domain-scoped when it holds per-domain state (like a persistent cache).
  • 1.1.8 / 1.1.8.1: when declared, the API MUST NOT bind that instance to more than one domain.

Stateless providers are unaffected and can still back multiple domains; only opt-in providers are constrained. This also resolves the open question about a shared instance only seeing its first domain.

OFREP cache key construction lives in the follow-up ADR: open-feature/protocol#80.

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
…ization

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@jonathannorris

Copy link
Copy Markdown
Member Author

Added 2.4.4: a domain-scoped provider MUST accept the bound domain at initialization. The domain-scoped declaration is only meaningful if the provider actually consumes the domain to scope its state, so this makes that an explicit contract rather than leaving it implied.

I kept it as a provider-side MUST rather than an API-enforced check, since whether the API can detect a violation varies and we can't rely on surfacing it as an error consistently.

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.

6 participants