From b73df07ab85daeeca4e624fe15bbc3a8ca6e2065 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Mon, 18 May 2026 12:28:00 -0400 Subject: [PATCH 1/8] fix: provider state ownership via events Providers emit events to signal all status transitions; the SDK derives provider status from these events rather than inferring from lifecycle method return values. - Add section 2.8 (Provider status) with requirements 2.8.1-2.8.4 - Add Condition 2.8.5 for simple providers (no lifecycle, no events) - Update 1.7.3/4/5 to reference provider events instead of init outcomes - Change 5.1.1 from MAY to MUST; reframe 5.3.1/2 as event-driven - Update 5.3.5 table (PROVIDER_CONTEXT_CHANGED -> READY, PROVIDER_RECONCILING -> RECONCILING) - Add non-normative threading note to 5.3.3 - Add Appendix E (Migrations) for legacy provider compatibility - Fix lint check for non-requirement entries Signed-off-by: Todd Baert --- specification.json | 63 ++++++++++++++++--- specification/README.md | 1 + specification/appendix-e-migrations.md | 44 +++++++++++++ specification/sections/01-flag-evaluation.md | 18 +++--- specification/sections/02-providers.md | 53 +++++++++++++++- specification/sections/05-events.md | 59 ++++++++--------- .../specification_parser/lint_json_output.py | 2 +- 7 files changed, 190 insertions(+), 50 deletions(-) create mode 100644 specification/appendix-e-migrations.md diff --git a/specification.json b/specification.json index 8362a3a82..02957d254 100644 --- a/specification.json +++ b/specification.json @@ -325,8 +325,8 @@ { "id": "Conditional Requirement 1.7.2.1", "machine_id": "conditional_requirement_1_7_2_1", - "content": "In addition to `NOT_READY`, `READY`, `STALE`, or `ERROR`, the `provider status` accessor must support possible value `RECONCILING`.", - "RFC 2119 keyword": null, + "content": "In addition to `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`, the `provider status` accessor MUST support possible value `RECONCILING`.", + "RFC 2119 keyword": "MUST", "children": [] } ] @@ -334,21 +334,21 @@ { "id": "Requirement 1.7.3", "machine_id": "requirement_1_7_3", - "content": "The client's `provider status` accessor MUST indicate `READY` if the `initialize` function of the associated provider terminates normally.", + "content": "The client's `provider status` accessor MUST indicate `READY` after the provider emits `PROVIDER_READY` following initialization.", "RFC 2119 keyword": "MUST", "children": [] }, { "id": "Requirement 1.7.4", "machine_id": "requirement_1_7_4", - "content": "The client's `provider status` accessor MUST indicate `ERROR` if the `initialize` function of the associated provider terminates abnormally.", + "content": "The client's `provider status` accessor MUST indicate `ERROR` after the provider emits `PROVIDER_ERROR` following initialization.", "RFC 2119 keyword": "MUST", "children": [] }, { "id": "Requirement 1.7.5", "machine_id": "requirement_1_7_5", - "content": "The client's `provider status` accessor MUST indicate `FATAL` if the `initialize` function of the associated provider terminates abnormally and indicates `error code` `PROVIDER_FATAL`.", + "content": "The client's `provider status` accessor MUST indicate `FATAL` after the provider emits `PROVIDER_ERROR` with error code `PROVIDER_FATAL` following initialization.", "RFC 2119 keyword": "MUST", "children": [] }, @@ -593,6 +593,49 @@ "RFC 2119 keyword": "MAY", "children": [] }, + { + "id": "Requirement 2.8.1", + "machine_id": "requirement_2_8_1", + "content": "The provider MUST emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `shutdown`, `on context change`) and spontaneous transitions.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 2.8.2", + "machine_id": "requirement_2_8_2", + "content": "The provider MUST emit `PROVIDER_READY` if its `initialize` function terminates normally.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 2.8.3", + "machine_id": "requirement_2_8_3", + "content": "The provider MUST emit `PROVIDER_ERROR` if its `initialize` function terminates abnormally.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 2.8.4", + "machine_id": "requirement_2_8_4", + "content": "The provider MUST emit `PROVIDER_CONTEXT_CHANGED` if its `on context changed` function terminates normally, and `PROVIDER_ERROR` if it terminates abnormally.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Condition 2.8.5", + "machine_id": "condition_2_8_5", + "content": "The provider does not define an `initialize` function or an event emission mechanism.", + "RFC 2119 keyword": null, + "children": [ + { + "id": "Conditional Requirement 2.8.5.1", + "machine_id": "conditional_requirement_2_8_5_1", + "content": "The SDK MUST treat such providers as `READY` from registration and MUST run `PROVIDER_READY` handlers on their behalf.", + "RFC 2119 keyword": "MUST", + "children": [] + } + ] + }, { "id": "Requirement 3.1.1", "machine_id": "requirement_3_1_1", @@ -1013,8 +1056,8 @@ { "id": "Requirement 5.1.1", "machine_id": "requirement_5_1_1", - "content": "The `provider` MAY define a mechanism for signaling the occurrence of one of a set of events, including `PROVIDER_READY`, `PROVIDER_ERROR`, `PROVIDER_CONFIGURATION_CHANGED` and `PROVIDER_STALE`, with a `provider event details` payload.", - "RFC 2119 keyword": "MAY", + "content": "The `feature provider` interface MUST define a mechanism for signaling the occurrence of one of a set of events, including `PROVIDER_READY`, `PROVIDER_ERROR`, `PROVIDER_CONFIGURATION_CHANGED`, `PROVIDER_STALE`, `PROVIDER_RECONCILING`, and `PROVIDER_CONTEXT_CHANGED`, with a `provider event details` payload.", + "RFC 2119 keyword": "MUST", "children": [] }, { @@ -1097,14 +1140,14 @@ { "id": "Requirement 5.3.1", "machine_id": "requirement_5_3_1", - "content": "If the provider's `initialize` function terminates normally, `PROVIDER_READY` handlers MUST run.", + "content": "When the provider emits `PROVIDER_READY`, associated `PROVIDER_READY` handlers MUST run.", "RFC 2119 keyword": "MUST", "children": [] }, { "id": "Requirement 5.3.2", "machine_id": "requirement_5_3_2", - "content": "If the provider's `initialize` function terminates abnormally, `PROVIDER_ERROR` handlers MUST run.", + "content": "When the provider emits `PROVIDER_ERROR`, associated `PROVIDER_ERROR` handlers MUST run.", "RFC 2119 keyword": "MUST", "children": [] }, @@ -1147,7 +1190,7 @@ { "id": "Requirement 5.3.5", "machine_id": "requirement_5_3_5", - "content": "If the provider emits an event, the value of the client's `provider status` MUST be updated to the status associated with that event **before** the SDK invokes any event handlers for that event, so that handlers observe a consistent status.", + "content": "When a provider emits an event, the SDK MUST update the `provider status` to the status associated with that event **before** invoking any event handlers for that event, so that handlers observe a consistent status.", "RFC 2119 keyword": "MUST", "children": [] }, diff --git a/specification/README.md b/specification/README.md index c39658d57..6772899d1 100644 --- a/specification/README.md +++ b/specification/README.md @@ -21,6 +21,7 @@ sidebar_position: 0 - [Appendix B: Gherkin Suites](./appendix-b-gherkin-suites.md) - [Appendix C: OFREP](./appendix-c/index.md) - [Appendix D: Observability](./appendix-d-observability.md) +- [Appendix E: Migrations](./appendix-e-migrations.md) ## Conformance diff --git a/specification/appendix-e-migrations.md b/specification/appendix-e-migrations.md new file mode 100644 index 000000000..fa69367de --- /dev/null +++ b/specification/appendix-e-migrations.md @@ -0,0 +1,44 @@ +--- +id: appendix-e +title: "Appendix E: Migrations" +description: Migration guidance for breaking spec changes +sidebar_position: 6 +--- + +# Appendix E: Migrations + +This appendix provides non-normative guidance for provider authors and SDK authors on migrating to new or changed specification requirements. + +## Provider Lifecycle Event Emission + +### Background + +Prior to `v0.9.0`, the SDK emitted synthetic lifecycle events (`PROVIDER_READY`, `PROVIDER_ERROR`) on behalf of providers after lifecycle methods (`initialize`, `shutdown`, `on context change`) returned. +This created a race condition in multi-threaded SDKs: the provider could emit events from background threads concurrently with SDK-emitted synthetic events, resulting in incorrect event ordering and inconsistent status. + +The spec now requires providers to emit their own lifecycle events. +The SDK derives provider status entirely from the provider's event stream (see [provider status](./sections/02-providers.md#28-provider-status), [requirement 5.3.5](./sections/05-events.md#requirement-535)). + +### For provider authors + +Providers must now emit events for all state transitions, including those resulting from lifecycle methods: + +- `PROVIDER_READY` after successful initialization +- `PROVIDER_ERROR` after failed initialization (with appropriate error code) +- `PROVIDER_RECONCILING` when beginning context reconciliation (static-context paradigm) +- `PROVIDER_CONTEXT_CHANGED` after successful context reconciliation (static-context paradigm) +- `PROVIDER_ERROR` after failed context reconciliation (static-context paradigm) + +To signal to the SDK that your provider emits its own lifecycle events, implement the opt-in marker defined by the SDK (e.g. an interface, boolean property, or type-level tag). + +Providers that do not implement this marker will continue to work via the SDK's legacy compatibility path (see below). + +### For SDK authors + +SDKs must detect whether a provider emits its own lifecycle events, via an opt-in marker (e.g. an interface, boolean property, or type-level tag). + +- **Marker present:** the SDK does not emit synthetic lifecycle events. It derives status solely from the provider's event stream. +- **Marker absent (legacy):** the SDK emits synthetic lifecycle events after lifecycle methods return, as before. This path is deprecated. + +The legacy path should be deprecated in the release that introduces the marker, with removal targeted for the next major version. +SDK authors should update any first-party providers and provider base classes to emit their own lifecycle events. diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 88ff29a20..64d79da7c 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -476,9 +476,9 @@ stateDiagram-v2 > The `client` **MUST** define a `provider status` accessor which indicates the readiness of the associated provider, with possible values `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`. -The SDK at all times maintains an up-to-date state corresponding to the success/failure of the last lifecycle method (`initialize`, `shutdown`, `on context change`) or emitted event. +The SDK derives provider status from events emitted by the provider and keeps it up-to-date as events are received. -see [provider status](../types.md#provider-status) +see [provider status](../types.md#provider-status), [provider events](./05-events.md#51-provider-events) #### Condition 1.7.2 @@ -490,27 +490,27 @@ see: [static-context paradigm](../glossary.md#static-context-paradigm) ##### Conditional Requirement 1.7.2.1 -> In addition to `NOT_READY`, `READY`, `STALE`, or `ERROR`, the `provider status` accessor must support possible value `RECONCILING`. +> In addition to `NOT_READY`, `READY`, `STALE`, `ERROR`, or `FATAL`, the `provider status` accessor **MUST** support possible value `RECONCILING`. In the static context paradigm, the implementation must define a `provider status` indicating that a provider is reconciling its internal state due to a context change. #### Requirement 1.7.3 -> The client's `provider status` accessor **MUST** indicate `READY` if the `initialize` function of the associated provider terminates normally. +> The client's `provider status` accessor **MUST** indicate `READY` after the provider emits `PROVIDER_READY` following initialization. -Once the provider has initialized, the `provider status` should indicate the provider is ready to be used to evaluate flags. +see: [provider status requirements](./02-providers.md#28-provider-status), [event-status mapping](./05-events.md#requirement-535) #### Requirement 1.7.4 -> The client's `provider status` accessor **MUST** indicate `ERROR` if the `initialize` function of the associated provider terminates abnormally. +> The client's `provider status` accessor **MUST** indicate `ERROR` after the provider emits `PROVIDER_ERROR` following initialization. -If the provider has failed to initialize, the `provider status` should indicate the provider is in an error state. +see: [provider status requirements](./02-providers.md#28-provider-status), [event-status mapping](./05-events.md#requirement-535) #### Requirement 1.7.5 -> The client's `provider status` accessor **MUST** indicate `FATAL` if the `initialize` function of the associated provider terminates abnormally and indicates `error code` `PROVIDER_FATAL`. +> The client's `provider status` accessor **MUST** indicate `FATAL` after the provider emits `PROVIDER_ERROR` with error code `PROVIDER_FATAL` following initialization. -If the provider has failed to initialize, the `provider status` should indicate the provider is in an error state. +see: [provider status requirements](./02-providers.md#28-provider-status), [event-status mapping](./05-events.md#requirement-535) #### Requirement 1.7.6 diff --git a/specification/sections/02-providers.md b/specification/sections/02-providers.md index 6a5e2ac1f..5b129c9f4 100644 --- a/specification/sections/02-providers.md +++ b/specification/sections/02-providers.md @@ -211,7 +211,7 @@ class MyProvider implements Provider { If a provider is unable to start up correctly, it should indicate abnormal execution by throwing an exception, returning an error, or otherwise indicating so by means idiomatic to the implementation language. If the error is irrecoverable (perhaps due to bad credentials or invalid configuration) the `PROVIDER_FATAL` error code should be used. -see: [error codes](../types.md#error-code) +see: [error codes](../types.md#error-code), [provider status](#28-provider-status) #### Requirement 2.4.3 @@ -290,6 +290,8 @@ class MyProvider implements Provider { } ``` +see: [provider status](#28-provider-status) + Providers may maintain remote connections, timers, threads or other constructs that need to be appropriately disposed of. Provider authors may implement a `shutdown` function to perform relevant clean-up actions. Alternatively, implementations might leverage language idioms such as auto-disposable interfaces or some means of cancellation signal propagation to allow for graceful shutdown. @@ -326,3 +328,52 @@ The track function performs side effects required to record the `tracking event` Providers should be careful to complete any communication or flush any relevant uncommitted tracking data before they shut down. See [shutdown](#25-shutdown). + +### 2.8. Provider status + +[![hardening](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) + +The SDK derives provider status from events emitted by the provider. +Providers signal all state transitions by emitting the appropriate event; the SDK updates its internal status accordingly and runs associated handlers. + +Providers that do not define lifecycle methods or an event emission mechanism cannot emit events by design; see Condition 2.8.5. +Requirements 2.8.1-2.8.4 apply only to providers that define lifecycle methods and an event emission mechanism. + +see: [provider lifecycle management](./01-flag-evaluation.md#17-provider-lifecycle-management), [provider events](./05-events.md#51-provider-events) + +#### Requirement 2.8.1 + +> The provider **MUST** emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `shutdown`, `on context change`) and spontaneous transitions. + +Providers must not rely on the SDK to infer status from lifecycle method return values. +Instead, the provider emits the appropriate event (e.g. `PROVIDER_READY` after successful initialization) to signal each transition. + +see: [provider events](./05-events.md#51-provider-events), [provider event types](../types.md#provider-events) + +#### Requirement 2.8.2 + +> The provider **MUST** emit `PROVIDER_READY` if its `initialize` function terminates normally. + +#### Requirement 2.8.3 + +> The provider **MUST** emit `PROVIDER_ERROR` if its `initialize` function terminates abnormally. + +If the error is irrecoverable, the error code must indicate `PROVIDER_FATAL`. + +see: [error codes](../types.md#error-code) + +#### Requirement 2.8.4 + +> The provider **MUST** emit `PROVIDER_CONTEXT_CHANGED` if its `on context changed` function terminates normally, and `PROVIDER_ERROR` if it terminates abnormally. + +see: [provider context reconciliation](#26-provider-context-reconciliation) + +#### Condition 2.8.5 + +> The provider does not define an `initialize` function or an event emission mechanism. + +##### Conditional Requirement 2.8.5.1 + +> The SDK **MUST** treat such providers as `READY` from registration and **MUST** run `PROVIDER_READY` handlers on their behalf. + +Such providers cannot emit their own events by design. diff --git a/specification/sections/05-events.md b/specification/sections/05-events.md index 17af33d41..0923213f7 100644 --- a/specification/sections/05-events.md +++ b/specification/sections/05-events.md @@ -31,10 +31,11 @@ see: [domain](../glossary.md#domain) #### Requirement 5.1.1 -> The `provider` **MAY** define a mechanism for signaling the occurrence of one of a set of events, including `PROVIDER_READY`, `PROVIDER_ERROR`, `PROVIDER_CONFIGURATION_CHANGED` and `PROVIDER_STALE`, with a `provider event details` payload. +> The `feature provider` interface **MUST** define a mechanism for signaling the occurrence of one of a set of events, including `PROVIDER_READY`, `PROVIDER_ERROR`, `PROVIDER_CONFIGURATION_CHANGED`, `PROVIDER_STALE`, `PROVIDER_RECONCILING`, and `PROVIDER_CONTEXT_CHANGED`, with a `provider event details` payload. -Providers cannot emit `PROVIDER_CONTEXT_CHANGED` or `PROVIDER_RECONCILING` events. -These are emitted only by the SDK during context reconciliation. +Providers must emit events to signal all state transitions, including those resulting from lifecycle methods (initialize, shutdown, reconciliation). +The SDK derives provider status from these events. +Providers without lifecycle methods or an event emission mechanism cannot emit events by design; see [Condition 2.8.5](./02-providers.md#condition-285). If available, native event-emitter or observable/observer language constructs can be used. @@ -138,22 +139,21 @@ This means that the order of provider configuration and event handler addition i ### Event handlers and initialization -Though providers themselves need not implement events, the `flag evaluation API` uses events to convey relevant state changes during configuration and initialization. -Implementations automatically emit `PROVIDER_READY` or `PROVIDER_ERROR` events depending on the outcome of the `initialize` function, if the provider has implemented one (if none is implemented, `PROVIDER_READY` runs unconditionally). +The provider signals successful or failed initialization by emitting `PROVIDER_READY` or `PROVIDER_ERROR` events respectively. _Application authors_ and _application integrators_ use these events to wait for proper initialization of the provider and to do basic monitoring and error handling. #### Requirement 5.3.1 -> If the provider's `initialize` function terminates normally, `PROVIDER_READY` handlers **MUST** run. +> When the provider emits `PROVIDER_READY`, associated `PROVIDER_READY` handlers **MUST** run. See [provider initialization](./02-providers.md#24-initialization) and [setting a provider](./01-flag-evaluation.md#setting-a-provider). #### Requirement 5.3.2 -> If the provider's `initialize` function terminates abnormally, `PROVIDER_ERROR` handlers **MUST** run. +> When the provider emits `PROVIDER_ERROR`, associated `PROVIDER_ERROR` handlers **MUST** run. A failed initialization could represent an unrecoverable error, such as bad credentials or a missing file. -If a failed initialization could also represent a transient error. +A failed initialization could also represent a transient error. A provider which maintains a persistent connection to a remote `flag management system` may attempt to reconnect, and emit `PROVIDER_READY` after a failed initialization. See [provider initialization](./02-providers.md#24-initialization) and [setting a provider](./01-flag-evaluation.md#setting-a-provider). @@ -166,16 +166,17 @@ Handlers may be attached at any point in the application lifecycle. Handlers should run immediately if the provider is already in the associated state. For instance, _application authors_ may attach readiness handlers to be confident that the system is ready to evaluate flags. If such handlers are attached after the provider underlying the client has already been initialized, they should run immediately. +In multi-threaded environments, implementations should ensure that running a handler immediately upon registration does not result in out-of-order event processing (e.g. by holding a lock on the event queue). See [provider initialization](./02-providers.md#24-initialization), [setting a provider](./01-flag-evaluation.md#setting-a-provider). ### Event handlers and context reconciliation Providers built to conform to the static context paradigm feature two additional events: `PROVIDER_RECONCILING` and `PROVIDER_CONTEXT_CHANGED`. -When the provider is reconciling its internal state (the `on context changed` function is running and not yet terminated), the SDK transitions the provider into state `RECONCILING` and then emits `PROVIDER_RECONCILING`. +When the provider is reconciling its internal state (the `on context changed` function is running and not yet terminated), the provider emits `PROVIDER_RECONCILING`. This can be particularly useful for displaying loading indicators while the [evaluation context](./03-evaluation-context.md) is being reconciled. -If the `on context changed` function terminates normally, the SDK transitions the provider into the `READY` state and then emits `PROVIDER_CONTEXT_CHANGED`; otherwise it transitions the provider into the `ERROR` state and then emits `PROVIDER_ERROR`. +If the `on context changed` function terminates normally, the provider emits `PROVIDER_CONTEXT_CHANGED`; otherwise it emits `PROVIDER_ERROR`. The `PROVIDER_CONTEXT_CHANGED` is used to signal that the associated context has been changed, and flags should be re-evaluated. This can be particularly useful for triggering UI repaints in multiple components when one component updates the [evaluation context](./03-evaluation-context.md). @@ -205,8 +206,8 @@ see: [static-context paradigm](../glossary.md#static-context-paradigm) > While the provider's `on context changed` function is executing, associated `RECONCILING` handlers **MUST** run. -The implementation must run any `RECONCILING` handlers associated with the provider while the provider is reconciling its state. -In languages with asynchronous semantics, the emission of this event can be skipped if the `on context changed` function of the provider in question executes synchronously for a given provider, no other operations can take place while it runs. +The provider must emit `PROVIDER_RECONCILING` while it is reconciling its state. +In languages with asynchronous semantics, the emission of this event can be skipped if the `on context changed` function of the provider in question executes synchronously. see: [provider event types](../types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](02-providers.md#26-provider-context-reconciliation) @@ -214,36 +215,36 @@ see: [provider event types](../types.md#provider-events), [provider events](#51- > If the provider's `on context changed` function terminates normally, and no other invocations have yet to terminate, associated `PROVIDER_CONTEXT_CHANGED` handlers **MUST** run. -The implementation must run any `PROVIDER_CONTEXT_CHANGED` handlers associated with the provider after the provider has reconciled its state and returned from the `on context changed` function. -The `PROVIDER_CONTEXT_CHANGED` is not emitted from the provider itself; the SDK implementation must run the `PROVIDER_CONTEXT_CHANGED` handlers if the `on context changed` function terminates normally. -It's possible that the `on context changed` function is invoked simultaneously or in quick succession; in this case the SDK will only run the `PROVIDER_CONTEXT_CHANGED` handlers after all reentrant invocations have terminated, and the last to terminate was successful (terminated normally). +The provider must emit `PROVIDER_CONTEXT_CHANGED` after it has successfully reconciled the context. +The `on context changed` function may be invoked simultaneously or in quick succession; the provider must be prepared to handle this case. + see: [provider event types](../types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](02-providers.md#26-provider-context-reconciliation) ##### Conditional Requirement 5.3.4.3 > If the provider's `on context changed` function terminates abnormally, and no other invocations have yet to terminate, associated `PROVIDER_ERROR` handlers **MUST** run. -The `PROVIDER_ERROR` is not emitted from the provider itself; the SDK implementation must run the `PROVIDER_ERROR` handlers if the `on context changed` throws or otherwise signals an error. -It's possible that the `on context changed` function is invoked simultaneously or in quick succession; in this case the SDK will only run the `PROVIDER_ERROR` handlers after all reentrant invocations have terminated, and the last to terminate was unsuccessful (terminated abnormally). +The provider must emit `PROVIDER_ERROR` if reconciling the context fails. +The `on context changed` function may be invoked simultaneously or in quick succession; the provider must be prepared to handle that. see: [provider event types](../types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](02-providers.md#26-provider-context-reconciliation) #### Requirement 5.3.5 -> If the provider emits an event, the value of the client's `provider status` **MUST** be updated to the status associated with that event **before** the SDK invokes any event handlers for that event, so that handlers observe a consistent status. +> When a provider emits an event, the SDK **MUST** update the `provider status` to the status associated with that event **before** invoking any event handlers for that event, so that handlers observe a consistent status. -Some providers may emit events spontaneously, based on changes in their internal state (connections, caches, etc). -The SDK must update its internal representation of the provider's state accordingly: +The SDK derives provider status entirely from events emitted by the provider. +The table below summarizes the association between events and provider status: -| Event | Associated Status | -| -------------------------------- | ------------------------------------------------------- | -| `PROVIDER_READY` | `READY` | -| `PROVIDER_STALE` | `STALE` | -| `PROVIDER_ERROR` | `ERROR`/`FATAL`* | -| `PROVIDER_CONFIGURATION_CHANGED` | N/A (provider remains in its current state) | -| `PROVIDER_CONTEXT_CHANGED` | N/A (only emitted by SDK during context reconciliation) | -| `PROVIDER_RECONCILING` | N/A (only emitted by SDK during context reconciliation) | +| Event | Associated Status | +| -------------------------------- | ------------------------------------------- | +| `PROVIDER_READY` | `READY` | +| `PROVIDER_STALE` | `STALE` | +| `PROVIDER_ERROR` | `ERROR`/`FATAL`* | +| `PROVIDER_CONFIGURATION_CHANGED` | N/A (provider remains in its current state) | +| `PROVIDER_CONTEXT_CHANGED` | `READY` | +| `PROVIDER_RECONCILING` | `RECONCILING` | \* If the `error code` associated with the error indicates `PROVIDER_FATAL`, the state is set to `FATAL` -see: [provider lifecycle management](01-flag-evaluation.md#17-provider-lifecycle-management), [provider status](../types.md#provider-status) [error codes](../types.md#error-code) +see: [provider lifecycle management](01-flag-evaluation.md#17-provider-lifecycle-management), [provider status](../types.md#provider-status), [error codes](../types.md#error-code) diff --git a/tools/specification_parser/lint_json_output.py b/tools/specification_parser/lint_json_output.py index 2073ee359..c3099fb1a 100644 --- a/tools/specification_parser/lint_json_output.py +++ b/tools/specification_parser/lint_json_output.py @@ -23,7 +23,7 @@ def main(f): try: for entry in entries: if entry.get('RFC 2119 keyword') is None and \ - 'condition' not in entry['id'].lower(): + 'requirement' in entry['id'].lower(): print(f"{jsonfile.name}: Rule {entry['id']} is missing a RFC 2119 keyword", file=sys.stderr) errors += 1 pass From a3d0455e38cdfaceea2afe8ef9b74eed4d242309 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 3 Jun 2026 13:12:22 -0400 Subject: [PATCH 2/8] fixup: remove short circuit Signed-off-by: Todd Baert --- specification.json | 18 ++---------------- specification/sections/01-flag-evaluation.md | 20 +------------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/specification.json b/specification.json index 02957d254..5900d3eba 100644 --- a/specification.json +++ b/specification.json @@ -355,27 +355,13 @@ { "id": "Requirement 1.7.6", "machine_id": "requirement_1_7_6", - "content": "The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `NOT_READY`.", - "RFC 2119 keyword": "MUST", - "children": [] - }, - { - "id": "Requirement 1.7.7", - "machine_id": "requirement_1_7_7", - "content": "The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `FATAL`.", - "RFC 2119 keyword": "MUST", - "children": [] - }, - { - "id": "Requirement 1.7.8", - "machine_id": "requirement_1_7_8", "content": "Implementations SHOULD propagate the `error code` returned from any provider lifecycle methods.", "RFC 2119 keyword": "SHOULD", "children": [] }, { - "id": "Requirement 1.7.9", - "machine_id": "requirement_1_7_9", + "id": "Requirement 1.7.7", + "machine_id": "requirement_1_7_7", "content": "The client's `provider status` accessor MUST indicate `NOT_READY` once the `shutdown` function of the associated provider terminates.", "RFC 2119 keyword": "MUST", "children": [] diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 64d79da7c..5bdcf107c 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -514,31 +514,13 @@ see: [provider status requirements](./02-providers.md#28-provider-status), [even #### Requirement 1.7.6 -> The client **MUST** default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `NOT_READY`. - -The client defaults and returns the `PROVIDER_NOT_READY` `error code` if evaluation is attempted before the provider is initialized (the provider is still in a `NOT_READY` state). -The SDK avoids calling the provider's resolver functions entirely ("short-circuits") if the provider is in this state. - -see: [error codes](../types.md#error-code), [flag value resolution](./02-providers.md#22-flag-value-resolution) - -#### Requirement 1.7.7 - -> The client **MUST** default, run error hooks, and indicate an error if flag resolution is attempted while the provider is in `FATAL`. - -The client defaults and returns the `PROVIDER_FATAL` `error code` if evaluation is attempted after the provider has transitioned to an irrecoverable error state. -The SDK avoids calling the provider's resolver functions entirely ("short-circuits") if the provider is in this state. - -see: [error codes](../types.md#error-code), [flag value resolution](./02-providers.md#22-flag-value-resolution) - -#### Requirement 1.7.8 - > Implementations **SHOULD** propagate the `error code` returned from any provider lifecycle methods. The SDK ensures that if the provider's lifecycle methods terminate with an `error code`, that error code is included in any associated error events and returned/thrown errors/exceptions. see: [error codes](../types.md#error-code) -#### Requirement 1.7.9 +#### Requirement 1.7.7 > The client's `provider status` accessor **MUST** indicate `NOT_READY` once the `shutdown` function of the associated provider terminates. From cf3dcc75ac719dd02b91180fd39dcf33a366101f Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 3 Jun 2026 13:24:26 -0400 Subject: [PATCH 3/8] fixup: gemini feedback Signed-off-by: Todd Baert --- specification.json | 2 +- specification/sections/02-providers.md | 2 +- specification/sections/05-events.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/specification.json b/specification.json index 5900d3eba..fa8aae7c6 100644 --- a/specification.json +++ b/specification.json @@ -582,7 +582,7 @@ { "id": "Requirement 2.8.1", "machine_id": "requirement_2_8_1", - "content": "The provider MUST emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `shutdown`, `on context change`) and spontaneous transitions.", + "content": "The provider MUST emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `on context changed`) and spontaneous transitions.", "RFC 2119 keyword": "MUST", "children": [] }, diff --git a/specification/sections/02-providers.md b/specification/sections/02-providers.md index 5b129c9f4..f82062448 100644 --- a/specification/sections/02-providers.md +++ b/specification/sections/02-providers.md @@ -343,7 +343,7 @@ see: [provider lifecycle management](./01-flag-evaluation.md#17-provider-lifecyc #### Requirement 2.8.1 -> The provider **MUST** emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `shutdown`, `on context change`) and spontaneous transitions. +> The provider **MUST** emit an event to signal each status transition, including transitions resulting from lifecycle methods (`initialize`, `on context changed`) and spontaneous transitions. Providers must not rely on the SDK to infer status from lifecycle method return values. Instead, the provider emits the appropriate event (e.g. `PROVIDER_READY` after successful initialization) to signal each transition. diff --git a/specification/sections/05-events.md b/specification/sections/05-events.md index 0923213f7..f00fc3461 100644 --- a/specification/sections/05-events.md +++ b/specification/sections/05-events.md @@ -33,7 +33,7 @@ see: [domain](../glossary.md#domain) > The `feature provider` interface **MUST** define a mechanism for signaling the occurrence of one of a set of events, including `PROVIDER_READY`, `PROVIDER_ERROR`, `PROVIDER_CONFIGURATION_CHANGED`, `PROVIDER_STALE`, `PROVIDER_RECONCILING`, and `PROVIDER_CONTEXT_CHANGED`, with a `provider event details` payload. -Providers must emit events to signal all state transitions, including those resulting from lifecycle methods (initialize, shutdown, reconciliation). +Providers must emit events to signal all state transitions, including those resulting from lifecycle methods (initialize, reconciliation). The SDK derives provider status from these events. Providers without lifecycle methods or an event emission mechanism cannot emit events by design; see [Condition 2.8.5](./02-providers.md#condition-285). From edabaee0f08ba3624a6d1aecb248101f53545bd1 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 30 Jun 2026 16:48:39 -0400 Subject: [PATCH 4/8] fixup: review feedback Signed-off-by: Todd Baert --- specification.json | 2 +- specification/sections/02-providers.md | 10 ++++++---- specification/sections/05-events.md | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/specification.json b/specification.json index fa8aae7c6..13402ca09 100644 --- a/specification.json +++ b/specification.json @@ -610,7 +610,7 @@ { "id": "Condition 2.8.5", "machine_id": "condition_2_8_5", - "content": "The provider does not define an `initialize` function or an event emission mechanism.", + "content": "The provider does not define an `initialize` function.", "RFC 2119 keyword": null, "children": [ { diff --git a/specification/sections/02-providers.md b/specification/sections/02-providers.md index f82062448..9ded01993 100644 --- a/specification/sections/02-providers.md +++ b/specification/sections/02-providers.md @@ -95,6 +95,7 @@ As indicated in the definition of the [`resolution details`](../types.md#resolut > In cases of abnormal execution, the `provider` **MUST** indicate an error using the idioms of the implementation language, with an associated `error code` and optional associated `error message`. The provider might throw an exception, return an error, or populate the `error code` object on the returned `resolution details` structure to indicate a problem during flag value resolution. +This includes situations where the provider is not yet initialized or has encountered an irrecoverable error; in such cases, the provider indicates the error (e.g. with error codes `PROVIDER_NOT_READY` or `PROVIDER_FATAL`), and the client returns the default value per Requirement 1.4.10. See [error code](../types.md#error-code) for details. @@ -336,8 +337,8 @@ See [shutdown](#25-shutdown). The SDK derives provider status from events emitted by the provider. Providers signal all state transitions by emitting the appropriate event; the SDK updates its internal status accordingly and runs associated handlers. -Providers that do not define lifecycle methods or an event emission mechanism cannot emit events by design; see Condition 2.8.5. -Requirements 2.8.1-2.8.4 apply only to providers that define lifecycle methods and an event emission mechanism. +Providers that do not define an `initialize` function are not required to emit events for initialization; see Condition 2.8.5. +Requirements 2.8.1-2.8.4 apply only to providers that define lifecycle methods. see: [provider lifecycle management](./01-flag-evaluation.md#17-provider-lifecycle-management), [provider events](./05-events.md#51-provider-events) @@ -370,10 +371,11 @@ see: [provider context reconciliation](#26-provider-context-reconciliation) #### Condition 2.8.5 -> The provider does not define an `initialize` function or an event emission mechanism. +> The provider does not define an `initialize` function. ##### Conditional Requirement 2.8.5.1 > The SDK **MUST** treat such providers as `READY` from registration and **MUST** run `PROVIDER_READY` handlers on their behalf. -Such providers cannot emit their own events by design. +Such providers have no initialization to wait for and no associated state transition to signal. +Nothing in this specification prevents such a provider from emitting `PROVIDER_ERROR` (or other events) spontaneously to signal a problem encountered outside of initialization; SDKs handle such events as outlined elsewhere. diff --git a/specification/sections/05-events.md b/specification/sections/05-events.md index f00fc3461..3aea61ade 100644 --- a/specification/sections/05-events.md +++ b/specification/sections/05-events.md @@ -215,8 +215,8 @@ see: [provider event types](../types.md#provider-events), [provider events](#51- > If the provider's `on context changed` function terminates normally, and no other invocations have yet to terminate, associated `PROVIDER_CONTEXT_CHANGED` handlers **MUST** run. -The provider must emit `PROVIDER_CONTEXT_CHANGED` after it has successfully reconciled the context. -The `on context changed` function may be invoked simultaneously or in quick succession; the provider must be prepared to handle this case. +The provider emits `PROVIDER_CONTEXT_CHANGED` upon successful reconciliation; the SDK runs the associated handlers when it receives the event, satisfying this requirement. +If `on context changed` is invoked simultaneously or in quick succession, the provider should emit `PROVIDER_CONTEXT_CHANGED` only after the last reentrant invocation terminates normally, to avoid spurious updates from intermediate reconciliations. see: [provider event types](../types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](02-providers.md#26-provider-context-reconciliation) @@ -224,8 +224,8 @@ see: [provider event types](../types.md#provider-events), [provider events](#51- > If the provider's `on context changed` function terminates abnormally, and no other invocations have yet to terminate, associated `PROVIDER_ERROR` handlers **MUST** run. -The provider must emit `PROVIDER_ERROR` if reconciling the context fails. -The `on context changed` function may be invoked simultaneously or in quick succession; the provider must be prepared to handle that. +The provider emits `PROVIDER_ERROR` upon failed reconciliation; the SDK runs the associated handlers when it receives the event, satisfying this requirement. +If `on context changed` is invoked simultaneously or in quick succession, the provider should emit `PROVIDER_ERROR` only after the last reentrant invocation terminates abnormally, to avoid spurious updates from intermediate reconciliations. see: [provider event types](../types.md#provider-events), [provider events](#51-provider-events), [provider context reconciliation](02-providers.md#26-provider-context-reconciliation) From 11d2d665ef786f0b21f2cfd203066c7d11e065bb Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 30 Jun 2026 16:53:42 -0400 Subject: [PATCH 5/8] fixup: gherkin Signed-off-by: Todd Baert --- specification/assets/gherkin/evaluation_v2.feature | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specification/assets/gherkin/evaluation_v2.feature b/specification/assets/gherkin/evaluation_v2.feature index 8754b1ebc..c1027e3a1 100644 --- a/specification/assets/gherkin/evaluation_v2.feature +++ b/specification/assets/gherkin/evaluation_v2.feature @@ -189,10 +189,10 @@ Feature: Flag Evaluations - Complete OpenFeature Specification Coverage | key | type | default | | boolean-flag | Object | {\"a\": 1} | - # Spec 1.7.6: Testing PROVIDER_NOT_READY error when provider isn't initialized - # Testing: client must short-circuit and return error when provider not ready + # Spec 2.2.7: Testing PROVIDER_NOT_READY error when provider isn't initialized + # Testing: provider indicates error abnormally; client returns default per 1.4.10 # Implicitly tests: 1.4.10 (client never throws exceptions - returns default instead) - @provider-status @spec-1.7.6 @spec-1.4.10 + @provider-status @spec-2.2.7 @spec-1.4.10 Scenario Outline: Provider not ready error Given a not ready provider And a -flag with key "" and a fallback value "" @@ -222,10 +222,10 @@ Feature: Flag Evaluations - Complete OpenFeature Specification Coverage | key | type | default | | object-flag | Object | {\"a\": 1} | - # Spec 1.7.7: Testing PROVIDER_FATAL error when provider is in fatal state - # Testing: client must short-circuit and return error when provider is fatal + # Spec 2.2.7: Testing PROVIDER_FATAL error when provider is in fatal state + # Testing: provider indicates error abnormally; client returns default per 1.4.10 # Implicitly tests: 1.4.10 (client never throws exceptions - returns default instead), 1.7.5 (FATAL status) - @provider-status @provider-status-fatal @spec-1.7.7 @spec-1.4.10 @spec-1.7.5 + @provider-status @provider-status-fatal @spec-2.2.7 @spec-1.4.10 @spec-1.7.5 Scenario Outline: Provider in fatal state error Given a fatal provider And a -flag with key "" and a fallback value "" From a337d32c91cae2589093d8a7e72747da30d26e4f Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 2 Jul 2026 08:10:07 -0400 Subject: [PATCH 6/8] fixup: clarify no shutdown event Signed-off-by: Todd Baert --- specification/appendix-e-migrations.md | 2 +- specification/sections/01-flag-evaluation.md | 1 + specification/sections/02-providers.md | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/specification/appendix-e-migrations.md b/specification/appendix-e-migrations.md index fa69367de..739acb27d 100644 --- a/specification/appendix-e-migrations.md +++ b/specification/appendix-e-migrations.md @@ -13,7 +13,7 @@ This appendix provides non-normative guidance for provider authors and SDK autho ### Background -Prior to `v0.9.0`, the SDK emitted synthetic lifecycle events (`PROVIDER_READY`, `PROVIDER_ERROR`) on behalf of providers after lifecycle methods (`initialize`, `shutdown`, `on context change`) returned. +Prior to `v0.9.0`, the SDK emitted synthetic lifecycle events (`PROVIDER_READY`, `PROVIDER_ERROR`) on behalf of providers after lifecycle methods (`initialize`, `shutdown`, `on context changed`) returned. This created a race condition in multi-threaded SDKs: the provider could emit events from background threads concurrently with SDK-emitted synthetic events, resulting in incorrect event ordering and inconsistent status. The spec now requires providers to emit their own lifecycle events. diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 5bdcf107c..676e5bedb 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -525,6 +525,7 @@ see: [error codes](../types.md#error-code) > The client's `provider status` accessor **MUST** indicate `NOT_READY` once the `shutdown` function of the associated provider terminates. Regardless of the success of the provider's `shutdown` function, the `provider status` should convey the provider is no longer ready to use once the shutdown function terminates. +Unlike other status transitions, this shutdown is inferred by the SDK; because the SDK initiates the shutdown call, no event from the provider is required. ### 1.8. Isolated API Instances diff --git a/specification/sections/02-providers.md b/specification/sections/02-providers.md index 9ded01993..c4ed42097 100644 --- a/specification/sections/02-providers.md +++ b/specification/sections/02-providers.md @@ -337,6 +337,8 @@ See [shutdown](#25-shutdown). The SDK derives provider status from events emitted by the provider. Providers signal all state transitions by emitting the appropriate event; the SDK updates its internal status accordingly and runs associated handlers. +Shutdown is the exception: the SDK initiates the `shutdown` call and infers the `NOT_READY` transition itself, so no event from the provider is required (see [Requirement 1.7.7](./01-flag-evaluation.md#requirement-177)). + Providers that do not define an `initialize` function are not required to emit events for initialization; see Condition 2.8.5. Requirements 2.8.1-2.8.4 apply only to providers that define lifecycle methods. From 6e2b8a337d321ce6e10571353d742ae92313d30c Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 2 Jul 2026 08:25:06 -0400 Subject: [PATCH 7/8] fixup: remove remaining references to synthetic events Signed-off-by: Todd Baert --- specification.json | 7 ------- specification/sections/01-flag-evaluation.md | 8 -------- specification/sections/02-providers.md | 7 ++++++- specification/sections/05-events.md | 6 +++--- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/specification.json b/specification.json index 13402ca09..573d6190a 100644 --- a/specification.json +++ b/specification.json @@ -355,13 +355,6 @@ { "id": "Requirement 1.7.6", "machine_id": "requirement_1_7_6", - "content": "Implementations SHOULD propagate the `error code` returned from any provider lifecycle methods.", - "RFC 2119 keyword": "SHOULD", - "children": [] - }, - { - "id": "Requirement 1.7.7", - "machine_id": "requirement_1_7_7", "content": "The client's `provider status` accessor MUST indicate `NOT_READY` once the `shutdown` function of the associated provider terminates.", "RFC 2119 keyword": "MUST", "children": [] diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 676e5bedb..035dfcb72 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -514,14 +514,6 @@ see: [provider status requirements](./02-providers.md#28-provider-status), [even #### Requirement 1.7.6 -> Implementations **SHOULD** propagate the `error code` returned from any provider lifecycle methods. - -The SDK ensures that if the provider's lifecycle methods terminate with an `error code`, that error code is included in any associated error events and returned/thrown errors/exceptions. - -see: [error codes](../types.md#error-code) - -#### Requirement 1.7.7 - > The client's `provider status` accessor **MUST** indicate `NOT_READY` once the `shutdown` function of the associated provider terminates. Regardless of the success of the provider's `shutdown` function, the `provider status` should convey the provider is no longer ready to use once the shutdown function terminates. diff --git a/specification/sections/02-providers.md b/specification/sections/02-providers.md index c4ed42097..02bdeac2f 100644 --- a/specification/sections/02-providers.md +++ b/specification/sections/02-providers.md @@ -337,7 +337,7 @@ See [shutdown](#25-shutdown). The SDK derives provider status from events emitted by the provider. Providers signal all state transitions by emitting the appropriate event; the SDK updates its internal status accordingly and runs associated handlers. -Shutdown is the exception: the SDK initiates the `shutdown` call and infers the `NOT_READY` transition itself, so no event from the provider is required (see [Requirement 1.7.7](./01-flag-evaluation.md#requirement-177)). +Shutdown is the exception: the SDK initiates the `shutdown` call and infers the `NOT_READY` transition itself, so no event from the provider is required (see [Requirement 1.7.6](./01-flag-evaluation.md#requirement-176)). Providers that do not define an `initialize` function are not required to emit events for initialization; see Condition 2.8.5. Requirements 2.8.1-2.8.4 apply only to providers that define lifecycle methods. @@ -362,6 +362,8 @@ see: [provider events](./05-events.md#51-provider-events), [provider event types > The provider **MUST** emit `PROVIDER_ERROR` if its `initialize` function terminates abnormally. If the error is irrecoverable, the error code must indicate `PROVIDER_FATAL`. +The provider is the sole source of this event; the SDK does not synthesize `PROVIDER_ERROR` on abnormal termination, even if `initialize` throws or returns an error. +The `initialize` return (or thrown error) is treated by the SDK as a synchronization signal only (e.g. to unblock `setProviderAndWait`); the status transition to `ERROR` or `FATAL` occurs only when the SDK receives the provider-emitted `PROVIDER_ERROR`. see: [error codes](../types.md#error-code) @@ -369,6 +371,9 @@ see: [error codes](../types.md#error-code) > The provider **MUST** emit `PROVIDER_CONTEXT_CHANGED` if its `on context changed` function terminates normally, and `PROVIDER_ERROR` if it terminates abnormally. +As with initialization, the provider is the sole source of these events; the SDK does not synthesize `PROVIDER_CONTEXT_CHANGED` or `PROVIDER_ERROR` based on the return of `on context changed`. +The `on context changed` return (or thrown error) is treated by the SDK as a synchronization signal only; the status transition and handler invocation occur only when the SDK receives the provider-emitted event. + see: [provider context reconciliation](#26-provider-context-reconciliation) #### Condition 2.8.5 diff --git a/specification/sections/05-events.md b/specification/sections/05-events.md index 3aea61ade..a205b9b66 100644 --- a/specification/sections/05-events.md +++ b/specification/sections/05-events.md @@ -23,7 +23,7 @@ graph ``` The `domain` of a provider constitutes a logical scope for events. -Clients associated with a particular provider through a `domain` run event handlers only when that provider emits events, or one of its lifecycle functions terminates. +Clients associated with a particular provider through a `domain` run event handlers only when that provider emits events. see: [domain](../glossary.md#domain) @@ -51,7 +51,7 @@ see: [provider event types](../types.md#provider-events), [`event details`](../t > When a `provider` signals the occurrence of a particular `event`, the associated `client` and `API` event handlers **MUST** run. Client event handlers respect the dynamic binding of clients to providers via `domains`. -Client event handlers run when a lifecycle function terminates on the associated provider, or the associated provider emits an event. +Client event handlers run when the associated provider emits an event. see: [provider event types](./../types.md#provider-events) and [event handlers](#52-event-handlers). @@ -60,7 +60,7 @@ see: [provider event types](./../types.md#provider-events) and [event handlers]( > When a `provider` signals the occurrence of a particular `event`, event handlers on clients which are not associated with that provider **MUST NOT** run. Client event handlers respect the dynamic binding of clients to providers via `domains`. -Client event handlers do not run when a lifecycle function terminates on an unassociated provider, or an unassociated provider emits an event. +Client event handlers do not run when an unassociated provider emits an event. see [setting a provider](./01-flag-evaluation.md#setting-a-provider), [domain](../glossary.md#domain) for details. From c9036fb016b7b8bce624e4bfb2555a82670e71f0 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 2 Jul 2026 09:25:26 -0400 Subject: [PATCH 8/8] fixup: clarify migration Signed-off-by: Todd Baert --- specification/appendix-e-migrations.md | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/specification/appendix-e-migrations.md b/specification/appendix-e-migrations.md index 739acb27d..119da1b57 100644 --- a/specification/appendix-e-migrations.md +++ b/specification/appendix-e-migrations.md @@ -33,6 +33,26 @@ To signal to the SDK that your provider emits its own lifecycle events, implemen Providers that do not implement this marker will continue to work via the SDK's legacy compatibility path (see below). +Providers with no `initialize` function are handled per [Condition 2.8.5](./sections/02-providers.md#condition-285) and require no changes. + +#### Common migration patterns + +**Blocking initialization.** +If `initialize` performs setup synchronously and returns when ready, emit `PROVIDER_READY` immediately before returning (or `PROVIDER_ERROR` before throwing). + +**Event-driven initialization.** +If setup completes asynchronously (e.g. the underlying vendor SDK signals readiness via its own event), forward that signal as `PROVIDER_READY`. +`initialize` may return before the event is emitted; the SDK treats the return as a synchronization signal only. + +**Context reconciliation.** +The provider now owns the coalescing behavior previously handled by the SDK. +If `on context changed` may be invoked simultaneously or in quick succession, emit `PROVIDER_CONTEXT_CHANGED` (or `PROVIDER_ERROR`) only after the last reentrant invocation terminates. + +#### Anti-patterns + +- Do not emit `PROVIDER_READY` before initialization work is complete. +- Do not rely on `initialize`'s return being observed by the SDK for status transitions; it is a synchronization signal only. + ### For SDK authors SDKs must detect whether a provider emits its own lifecycle events, via an opt-in marker (e.g. an interface, boolean property, or type-level tag). @@ -42,3 +62,15 @@ SDKs must detect whether a provider emits its own lifecycle events, via an opt-i The legacy path should be deprecated in the release that introduces the marker, with removal targeted for the next major version. SDK authors should update any first-party providers and provider base classes to emit their own lifecycle events. + +#### Legacy path behavior + +For providers without the marker, the SDK preserves pre-`v0.9.0` behavior: + +- Emit `PROVIDER_READY` after `initialize` returns normally. +- Emit `PROVIDER_ERROR` (with the returned error code, if any) after `initialize` returns abnormally. +- For static-context providers: coalesce reentrant `on context changed` invocations and emit `PROVIDER_CONTEXT_CHANGED` or `PROVIDER_ERROR` after the last one terminates. + +If a legacy provider also emits its own events, the SDK processes them per [requirement 5.3.5](./sections/05-events.md#requirement-535); duplicates are possible in this mixed mode and are expected legacy behavior. + +SDKs should log a deprecation warning on registration of a legacy provider.