From c1792369e3f61bc7116ca9b4a9e5fac8986f77ce Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Mon, 17 Oct 2022 11:00:13 -0400 Subject: [PATCH] chore: section clarity, harden API, providers (#149) **Marking Evaluation API and Providers `hardening`** and a few non-functional changes here that improve structure. specifically: - explicitly mark and number all sections - section headings have consistent numbering (provider section numbering was different than the other docs) - removed "draft" language An alternative to marking all of `provider` and `evaluation API` as hardening would be just marking all existing sections therein as hardening, which might be better since we'd have to do that anyway if we added a new experimental section. see: https://github.com/open-feature/spec/issues/146 for more on release goals. Signed-off-by: Todd Baert --- README.md | 2 +- specification.json | 56 +++++++++---------- specification/README.md | 6 +- specification/sections/01-flag-evaluation.md | 16 ++++-- specification/sections/02-providers.md | 38 +++++++------ .../sections/03-evaluation-context.md | 6 +- specification/sections/04-hooks.md | 10 ++-- 7 files changed, 70 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 0a14bdb4..3166486a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OpenFeature Specification (Draft) +# OpenFeature Specification [![Roadmap](https://img.shields.io/static/v1?label=Roadmap&message=public&color=green)](https://github.com/orgs/open-feature/projects/1) [![Contributing](https://img.shields.io/static/v1?label=Contributing&message=guide&color=blue)](https://github.com/open-feature/.github/blob/main/CONTRIBUTING.md) [![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/open-feature/.github/blob/main/CODE_OF_CONDUCT.md) diff --git a/specification.json b/specification.json index 79f759f6..c5286718 100644 --- a/specification.json +++ b/specification.json @@ -185,28 +185,28 @@ "children": [] }, { - "id": "Requirement 2.1", - "machine_id": "requirement_2_1", + "id": "Requirement 2.1.1", + "machine_id": "requirement_2_1_1", "content": "The provider interface MUST define a `metadata` member or accessor, containing a `name` field or accessor of type string, which identifies the provider implementation.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 2.2", - "machine_id": "requirement_2_2", + "id": "Requirement 2.2.1", + "machine_id": "requirement_2_2_1", "content": "The `feature provider` interface MUST define methods to resolve flag values, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required) and `evaluation context` (optional), which returns a `flag resolution` structure.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Condition 2.3", - "machine_id": "condition_2_3", + "id": "Condition 2.2.2", + "machine_id": "condition_2_2_2", "content": "The implementing language type system differentiates between strings, numbers, booleans and structures.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 2.3.1", - "machine_id": "conditional_requirement_2_3_1", + "id": "Conditional Requirement 2.2.2.1", + "machine_id": "conditional_requirement_2_2_2_1", "content": "The `feature provider` interface MUST define methods for typed flag resolution, including boolean, numeric, string, and structure.", "RFC 2119 keyword": "MUST", "children": [] @@ -214,49 +214,49 @@ ] }, { - "id": "Requirement 2.4", - "machine_id": "requirement_2_4", + "id": "Requirement 2.2.3", + "machine_id": "requirement_2_2_3", "content": "In cases of normal execution, the `provider` MUST populate the `flag resolution` structure's `value` field with the resolved flag value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 2.5", - "machine_id": "requirement_2_5", + "id": "Requirement 2.2.4", + "machine_id": "requirement_2_2_4", "content": "In cases of normal execution, the `provider` SHOULD populate the `flag resolution` structure's `variant` field with a string identifier corresponding to the returned flag value.", "RFC 2119 keyword": "SHOULD", "children": [] }, { - "id": "Requirement 2.6", - "machine_id": "requirement_2_6", + "id": "Requirement 2.2.5", + "machine_id": "requirement_2_2_5", "content": "The `provider` SHOULD populate the `flag resolution` structure's `reason` field with `\"DEFAULT\",` `\"TARGETING_MATCH\"`, `\"SPLIT\"`, `\"DISABLED\"`, `\"UNKNOWN\"`, `\"ERROR\"` or some other string indicating the semantic reason for the returned flag value.", "RFC 2119 keyword": "SHOULD", "children": [] }, { - "id": "Requirement 2.7", - "machine_id": "requirement_2_7", + "id": "Requirement 2.2.6", + "machine_id": "requirement_2_2_6", "content": "In cases of normal execution, the `provider` MUST NOT populate the `flag resolution` structure's `error code` field, or otherwise must populate it with a null or falsy value.", "RFC 2119 keyword": "MUST NOT", "children": [] }, { - "id": "Requirement 2.8", - "machine_id": "requirement_2_8", + "id": "Requirement 2.2.7", + "machine_id": "requirement_2_2_7", "content": "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`.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Condition 2.9", - "machine_id": "condition_2_9", + "id": "Condition 2.2.8", + "machine_id": "condition_2_2_8", "content": "The implementation language supports generics (or an equivalent feature).", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 2.9.1", - "machine_id": "conditional_requirement_2_9_1", + "id": "Conditional Requirement 2.2.8.1", + "machine_id": "conditional_requirement_2_2_8_1", "content": "The `flag resolution` structure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field.", "RFC 2119 keyword": "SHOULD", "children": [] @@ -264,22 +264,22 @@ ] }, { - "id": "Requirement 2.10", - "machine_id": "requirement_2_10", + "id": "Requirement 2.3.1", + "machine_id": "requirement_2_3_1", "content": "The provider interface MUST define a `provider hook` mechanism which can be optionally implemented in order to add `hook` instances to the evaluation life-cycle.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 2.11", - "machine_id": "requirement_2_11", + "id": "Requirement 2.3.2", + "machine_id": "requirement_2_3_2", "content": "In cases of normal execution, the `provider` MUST NOT populate the `flag resolution` structure's `error message` field, or otherwise must populate it with a null or falsy value.", "RFC 2119 keyword": "MUST NOT", "children": [] }, { - "id": "Requirement 2.12", - "machine_id": "requirement_2_12", + "id": "Requirement 2.3.3", + "machine_id": "requirement_2_3_3", "content": "In cases of abnormal execution, the `evaluation details` structure's `error message` field MAY contain a string containing additional detail about the nature of the error.", "RFC 2119 keyword": "MAY", "children": [] diff --git a/specification/README.md b/specification/README.md index 00c9e3cc..1f22ffd6 100644 --- a/specification/README.md +++ b/specification/README.md @@ -44,7 +44,7 @@ Possible statuses are described below: ### Experimental -[![Status](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) +[![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) Specification sections that are marked as `Experimental` contain functionality under active development. Breaking changes are allowed and may be made without deprecation notices or warnings with minor version updates. We recommend you use these features in experimental environments and not in production. @@ -54,7 +54,7 @@ Put simply: ### Hardening -[![Status](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) +[![hardening](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) Sections marked as `Hardening` describe functionality with an emphasis on stabilizing existing requirements. Breaking changes require consensus by the [Technical Steering Committee](https://github.com/open-feature/community/blob/main/governance-charter.md#tsc-members) but may still be made with minor version updates. These features are suitable for use in production environments. Feedback is encouraged. @@ -64,7 +64,7 @@ Put simply: ### Stable -[![Status](https://img.shields.io/static/v1?label=Status&message=stable&color=green)](https://github.com/open-feature/spec/tree/main/specification#stable) +[![stable](https://img.shields.io/static/v1?label=Status&message=stable&color=green)](https://github.com/open-feature/spec/tree/main/specification#stable) Sections marked as `Stable` do not allow breaking changes without a major version update. They can be used in production with a high degree of confidence. diff --git a/specification/sections/01-flag-evaluation.md b/specification/sections/01-flag-evaluation.md index 4ef3a3c8..c4594906 100644 --- a/specification/sections/01-flag-evaluation.md +++ b/specification/sections/01-flag-evaluation.md @@ -4,15 +4,15 @@ description: The specification that defines the developer facing feature flag ev toc_max_heading_level: 4 --- -# Flag Evaluation API +# 1. Flag Evaluation API -**Status**: [Experimental](../README.md#document-statuses) +[![hardening](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ## Overview The `evaluation API` allows for the evaluation of feature flag values, independent of any flag control plane or vendor. In the absence of a [provider](./02-providers.md) the `evaluation API` uses the "No-op provider", which simply returns the supplied default flag value. -### API Initialization and Configuration +### 1.1. API Initialization and Configuration #### Requirement 1.1.1 @@ -74,7 +74,7 @@ The name is a logical identifier for the client. Clients may be created in critical code paths, and even per-request in server-side HTTP contexts. Therefore, in keeping with the principle that OpenFeature should never cause abnormal execution of the first party application, this function should never throw. Abnormal execution in initialization should instead occur during provider registration. -### Client Usage +### 1.2. Client Usage #### Requirement 1.2.1 @@ -95,7 +95,9 @@ See [hooks](./04-hooks.md) for details. client.getMetadata().getName(); // "my-client" ``` -#### Flag Evaluation +#### 1.3. Flag Evaluation + +[![hardening](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ##### Requirement 1.3.1 @@ -138,7 +140,9 @@ See [types](../types.md) for details. > The `client` **SHOULD** guarantee the returned value of any typed flag evaluation method is of the expected type. If the value returned by the underlying provider implementation does not match the expected type, it's to be considered abnormal execution, and the supplied `default value` should be returned. -#### Detailed Flag Evaluation +#### 1.4. Detailed Flag Evaluation + +[![hardening](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ##### Requirement 1.4.1 diff --git a/specification/sections/02-providers.md b/specification/sections/02-providers.md index 6d3f06eb..be8ea16d 100644 --- a/specification/sections/02-providers.md +++ b/specification/sections/02-providers.md @@ -4,7 +4,9 @@ description: The specification that defines the responsibilities and behaviors o toc_max_heading_level: 4 --- -# Provider +# 2. Provider + +[![hardening](https://img.shields.io/static/v1?label=Status&message=hardening&color=yellow)](https://github.com/open-feature/spec/tree/main/specification#hardening) ## Overview @@ -14,9 +16,9 @@ Providers are the "translator" between the flag evaluation calls made in applica ![Provider](../assets/images/provider.png) -### Feature Provider Interface +### 2.1. Feature Provider Interface -#### Requirement 2.1 +#### Requirement 2.1.1 > The provider interface **MUST** define a `metadata` member or accessor, containing a `name` field or accessor of type string, which identifies the provider implementation. @@ -24,11 +26,11 @@ Providers are the "translator" between the flag evaluation calls made in applica provider.getMetadata().getName(); // "my-custom-provider" ``` -#### Flag Value Resolution +### 2.2 Flag Value Resolution `Providers` are implementations of the `feature provider` interface, which may wrap vendor SDKs, REST API clients, or otherwise resolve flag values from the runtime environment. -##### Requirement 2.2 +##### Requirement 2.2.1 > The `feature provider` interface **MUST** define methods to resolve flag values, with parameters `flag key` (string, required), `default value` (boolean | number | string | structure, required) and `evaluation context` (optional), which returns a `flag resolution` structure. @@ -39,11 +41,11 @@ resolveBooleanValue(flagKey, defaultValue, context); see: [flag resolution structure](../types.md#flag-resolution), [flag value resolution](../glossary.md#flag-value-resolution) -##### Condition 2.3 +##### Condition 2.2.2 > The implementing language type system differentiates between strings, numbers, booleans and structures. -###### Conditional Requirement 2.3.1 +###### Conditional Requirement 2.2.2.1 > The `feature provider` interface **MUST** define methods for typed flag resolution, including boolean, numeric, string, and structure. @@ -61,11 +63,11 @@ ResolutionDetails resolveNumberValue(string flagKey, number defaultValue, contex ResolutionDetails resolveStructureValue(string flagKey, JsonObject defaultValue, context: EvaluationContext); ``` -##### Requirement 2.4 +##### Requirement 2.2.3 > In cases of normal execution, the `provider` **MUST** populate the `flag resolution` structure's `value` field with the resolved flag value. -##### Requirement 2.5 +##### Requirement 2.2.4 > In cases of normal execution, the `provider` **SHOULD** populate the `flag resolution` structure's `variant` field with a string identifier corresponding to the returned flag value. @@ -73,17 +75,17 @@ For example, the flag value might be `3.14159265359`, and the variant field's va The value of the variant field might only be meaningful in the context of the flag management system associated with the provider. For example, the variant may be a UUID corresponding to the variant in the flag management system, or an index corresponding to the variant in the flag management system. -##### Requirement 2.6 +##### Requirement 2.2.5 > The `provider` **SHOULD** populate the `flag resolution` structure's `reason` field with `"DEFAULT",` `"TARGETING_MATCH"`, `"SPLIT"`, `"DISABLED"`, `"UNKNOWN"`, `"ERROR"` or some other string indicating the semantic reason for the returned flag value. As indicated in the definition of the [`flag resolution`](../types.md#resolution-details) structure, the `reason` should be a string. This allows providers to reflect accurately why a flag was resolved to a particular value. -##### Requirement 2.7 +##### Requirement 2.2.6 > In cases of normal execution, the `provider` **MUST NOT** populate the `flag resolution` structure's `error code` field, or otherwise must populate it with a null or falsy value. -##### Requirement 2.8 +##### Requirement 2.2.7 > 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`. @@ -96,11 +98,11 @@ See [error code](../types.md#error-code) for details. throw new ProviderError(ErrorCode.INVALID_CONTEXT, "The 'foo' attribute must be a string."); ``` -##### Condition 2.9 +##### Condition 2.2.8 > The implementation language supports generics (or an equivalent feature). -###### Conditional Requirement 2.9.1 +###### Conditional Requirement 2.2.8.1 > The `flag resolution` structure **SHOULD** accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped `value` field. @@ -118,11 +120,11 @@ ResolutionDetails resolveNumberValue(string flagKey, number defaultValue ResolutionDetails resolveStructureValue(string flagKey, MyStruct defaultValue, context: EvaluationContext); ``` -#### Provider hooks +#### 2.3. Provider hooks A `provider hook` exposes a mechanism for `provider authors` to register [`hooks`](./04-hooks.md) to tap into various stages of the flag evaluation lifecycle. These hooks can be used to perform side effects and mutate the context for purposes of the provider. Provider hooks are not configured or controlled by the `application author`. -##### Requirement 2.10 +##### Requirement 2.3.1 > The provider interface **MUST** define a `provider hook` mechanism which can be optionally implemented in order to add `hook` instances to the evaluation life-cycle. @@ -141,10 +143,10 @@ class MyProvider implements Provider { } ``` -#### Requirement 2.11 +#### Requirement 2.3.2 > In cases of normal execution, the `provider` **MUST NOT** populate the `flag resolution` structure's `error message` field, or otherwise must populate it with a null or falsy value. -#### Requirement 2.12 +#### Requirement 2.3.3 > In cases of abnormal execution, the `evaluation details` structure's `error message` field **MAY** contain a string containing additional detail about the nature of the error. diff --git a/specification/sections/03-evaluation-context.md b/specification/sections/03-evaluation-context.md index 1d46df41..f484d4e9 100644 --- a/specification/sections/03-evaluation-context.md +++ b/specification/sections/03-evaluation-context.md @@ -4,7 +4,7 @@ description: The specification that defines the structure and expectations of ev toc_max_heading_level: 4 --- -# Evaluation Context +# 3. Evaluation Context **Status**: [Experimental](../README.md#document-statuses) @@ -14,7 +14,7 @@ The `evaluation context` provides ambient information for the purposes of flag e The context might contain information about the end-user, the application, the host, or any other ambient data that might be useful in flag evaluation. For example, a flag system might define rules that return a specific value based on the user's email address, locale, or the time of day. The context provides this information. The context can be optionally provided at evaluation, and mutated in [before hooks](./04-hooks.md). -### Fields +### 3.1 Fields NOTE: Field casing is not specified, and should be chosen in accordance with language idioms. @@ -42,7 +42,7 @@ see: [structure](../types.md#structure), [datetime](../types.md#datetime) The key uniquely identifies a field in the `evaluation context` and it should be unique across all types to avoid any collision when marshalling the `evaluation context` by the provider. -### Merging Context +### 3.2 Merging Context #### Requirement 3.2.1 diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index 6c46e2ab..b1e41e1f 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -4,7 +4,7 @@ description: The specification that defines the expectations and life cycle of h toc_max_heading_level: 4 --- -# Hooks +# 4. Hooks ## Overview @@ -25,7 +25,7 @@ Hooks can be configured to run globally (impacting all flag evaluations), per cl **Hook**: Application author/integrator-supplied logic that is called by the OpenFeature framework at a specific stage. **Stage**: An explicit portion of the flag evaluation lifecycle. e.g. `before` being "before the [resolution](../glossary.md#resolving-flag-values) is run. **Invocation**: A single call to evaluate a flag. `client.getBooleanValue(..)` is an invocation. **API**: The global API singleton. -### Hook context +### 4.1. Hook context Hook context exists to provide hooks with information about the invocation. @@ -45,7 +45,7 @@ Hook context exists to provide hooks with information about the invocation. > The evaluation context **MUST** be mutable only within the `before` hook. -### Hook Hints +### 4.2. Hook Hints #### Requirement 4.2.1 @@ -67,7 +67,7 @@ Hook context exists to provide hooks with information about the invocation. > Condition: The provider `metadata` field in the `hook context` **MUST** be immutable. -### Hook creation and parameters +### 4.3. Hook creation and parameters #### Requirement 4.3.1 @@ -111,7 +111,7 @@ Evaluation context merge order is defined in [Requirement 3.2.2](./03-evaluation > Instead of `finally`, `finallyAfter` **SHOULD** be used. -### Hook registration & ordering +### 4.4. Hook registration & ordering #### Requirement 4.4.1