feat: add provider-side event emission helper - #261
Closed
mtonko-flx wants to merge 4 commits into
Closed
Conversation
Spec requirement 5.1.1 requires the provider event set to include PROVIDER_RECONCILING and PROVIDER_CONTEXT_CHANGED, and 5.2.3 requires event details to carry the provider name. Neither existed, so the SDK could not represent context reconciliation as events nor attribute an event to its provider. Signed-off-by: Mark Tonkonoh <mark.tonkonoh@fluxon.com>
BREAKING CHANGE: statusFlow is now StateFlow<OpenFeatureStatus> and conflates consecutive equal values. Signed-off-by: Mark Tonkonoh <mark.tonkonoh@fluxon.com>
…cle events The SDK inferred provider readiness from lifecycle methods returning: it published Ready once initialize returned, and drove the Reconciling/Ready transitions around onContextSet itself. A provider doing work in the background could report something in that window and have the SDK's own conclusion overwrite it. Providers can now declare, by implementing StateManagingProvider, that they report their own lifecycle events. For those the SDK publishes nothing of its own and reports exactly what the provider signals, in the order signalled. Note the marker carries no status accessor: the provider owns the events, the SDK still owns the status. Providers that do not implement it keep working unchanged. Everything the SDK used to do for all providers now lives in LegacyProviderWrapper, installed for them at registration and nowhere else, so withdrawing the behavior later is a deletion rather than an untangling. That includes coalescing overlapping context reconciliations, which moved out of the API instance along with its generation counters. Where such a provider already reports a lifecycle event itself, the wrapper stands aside instead of reporting the same transition twice. Registering a legacy provider logs a deprecation warning. setProviderAndWait now settles on the provider's reported event rather than on initialize returning. A provider that returns from initialize without reporting anything is in breach of its contract; the SDK logs that and keeps waiting rather than substituting a status, since substituting one is what caused the original race. Applications needing a bound can impose their own. Two consequences worth noting for anyone reading the test diff. Status is now observable only once the event carrying it has been dispatched, so tests asserting status straight after a gated lifecycle call have to let the dispatcher run. Registration settling on an event also means a collector started alongside it observes NotReady first. SpyProvider gained real metadata: provider names are now read during registration to attribute events, and a test double throwing from metadata broke that. BREAKING CHANGE: providers wanting to own their lifecycle events implement StateManagingProvider; the SDK synthesizing them is deprecated. setProviderAndWait waits for the provider's lifecycle event rather than for initialize to return. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implementing StateManagingProvider by hand means holding an event flow, picking a replay policy that does not lose a readiness report emitted before the SDK has subscribed, keeping emissions ordered under concurrency, and coalescing context reconciliations that overlap so an intermediate one does not surface as a spurious update. That is the same work for every provider author and easy to get subtly wrong. ProviderEventEmitter does it: a stream to return from observe(), an ordered emit for transitions that arise on their own, and initializing/reconciling wrappers that report the surrounding transitions and rethrow. Cancelling every reconciliation in flight re-reports the transition that preceded it, rather than leaving the SDK reconciling with nothing further coming. It holds no status. The provider owns its events and the SDK derives the status, so there is nothing here to read or set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueComment |
Author
|
Superseded by #262, which delivers this work as a single change. Closing the stack. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Now that a provider can own its lifecycle events by implementing
StateManagingProvider, doing so by hand is more work than it looks: hold an event flow, pick a replay policy that does not lose a readiness report emitted before the SDK has finished subscribing, keep emissions ordered under concurrency, and coalesce context reconciliations that overlap so an intermediate one does not surface as a spurious update. This addsProviderEventEmitterso provider authors do not each reimplement it.Motivation
Appendix E's migration guidance asks providers to emit readiness, failure and reconciliation events themselves, and spells out the coalescing rule as prose. Every provider author hitting that will write the same flow plumbing, and the failure modes are quiet ones — a lost readiness report leaves the SDK waiting, and an uncoalesced reconciliation publishes updates the application should never have seen.
This is the helper referred to in the migration path; it does not satisfy any requirement by itself.
Spec Requirements
Changes
Implementation
observe()— the stream to return fromStateManagingProvider.observe(), replaying its most recent event so a readiness report is not lost to a subscription still in progress.emit(event)— ordered reporting for transitions that arise on their own, such as going stale or recovering.initializing { … }— reports ready on success, error on failure, then rethrows.reconciling { … }— reports that reconciliation began, then its outcome, coalescing overlapping invocations so only the first announces and only the last reports. Where every invocation was cancelled it re-reports the transition that preceded reconciliation, rather than leaving the SDK reconciling with nothing further coming.It holds no status. The provider owns its events and the SDK derives the status from them, which is the distinction #385 chose over #380, so there is deliberately nothing here to read or set.
Usage Examples
Testing
PROVIDER_CONFIGURATION_CHANGEDis not what gets re-reported.ktlintCheckandapiCheckgreen,apiDumpregenerated.Breaking Changes
None — new public type, nothing existing changes. Using it is optional; a provider can implement
StateManagingProviderwith its own flow.