Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 55 additions & 26 deletions specification.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,57 +310,43 @@
{
"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": []
}
]
},
{
"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": []
},
{
"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",

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.

Nit (and very much a question to learn): I'm not very familiar with how these work, but assuming that code/tests are being generated from them. Is this re-numbering a good idea?

"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": []
Expand Down Expand Up @@ -564,6 +550,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`, `on context changed`) 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",
Expand Down Expand Up @@ -984,8 +1013,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": []
},
{
Expand Down Expand Up @@ -1068,14 +1097,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": []
},
Expand Down Expand Up @@ -1118,7 +1147,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": []
},
Expand Down
1 change: 1 addition & 0 deletions specification/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 44 additions & 0 deletions specification/appendix-e-migrations.md
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +43 to +44

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.

This makes much sense to me!

38 changes: 10 additions & 28 deletions specification/sections/01-flag-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,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

Expand All @@ -477,55 +477,37 @@ 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

> 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)
Comment on lines -504 to -518

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.

Do we have the client defaults case on evaluation during NOT_READY/FATAL defined somewhere else?
I guess we'll need to drop the "short-circuit" parts here but just want to make sure we keep the definitions on the defaults.

We could consider moving this into 1.4 alongside 1.4.10, without the short circuit of course.


#### 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.

Expand Down
53 changes: 52 additions & 1 deletion specification/sections/02-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,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)

### 2.5. Shutdown

Expand Down Expand Up @@ -269,6 +269,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.
Expand Down Expand Up @@ -305,3 +307,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.
Comment on lines +318 to +319

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.

Can a provider without lifecycle methods but an event emission mechanism emit events?
I feel like in this case this works. But is does not work if the provider has lifecycle methods but no event emission mechanism. Right?
I feel like 2.8.1 indirectly requires an eventing mechanism if there is lifecycle methods.


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`, `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.

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`.
Comment on lines +338 to +340

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.

in Requirement 5.3.5 of the current spec we say:

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:

Should we add a requirement here that says that providers may emit a PROVIDER_ERROR at any time? I know that we also not say that they can't but I think it would make it clearer.
This would make the non normative from the beginning more misleading as the provider may error without initialization:
Providers that do not define lifecycle methods or an event emission mechanism cannot emit events by design


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.

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.

As above I am confused by the or.
If a provider does not have an initialize function we run PROVIDER_READY.
If I get 2.8.1 right, it could basically be:

Suggested change
> The provider does not define an `initialize` function or an event emission mechanism.
> The provider does not define an `initialize` function.

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.

Also a bit confused.
With 5.1.1 going from MAY to MUST, does that mean providers without an event mechanism are not conformant to this version of the spec?
With this condition it seems we (SDK) intentionally support them. I guess that is just for the time being? Or expected to long term?


##### 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.
Loading
Loading