From d391718ae4d5432b7c8c080ba4b1ff38b9a75867 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 30 Sep 2024 08:43:21 -0700 Subject: [PATCH 01/10] feat: Add hook-data concept for hooks. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification/sections/04-hooks.md | 143 ++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 31 deletions(-) diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index af7bbaa6..5ee024f7 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -31,7 +31,9 @@ Hooks can be configured to run globally (impacting all flag evaluations), per cl ### Definitions -**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**: 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. ### 4.1. Hook context @@ -81,27 +83,106 @@ see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm) > Condition: The provider `metadata` field in the `hook context` **MUST** be immutable. -### 4.3. Hook creation and parameters +### 4.3. Hook data + +Hook data exists to allow hook stages to share data for a specific evaluation. For instance a span +for OpenTelemetry could be created in a `before` stage and closed in an `after` stage. + +```Java + public Optional before(HookContext context, HookHints hints, HookData data) { + SpanBuilder builder = tracer.spanBuilder('sample') + .setParent(Context.current().with(Span.current())); + Span span = builder.startSpan() + data.set("span", span); + } + + public void after(HookContext context, FlagEvaluationDetails details, HookHints hints, HookData data) { + // Only accessible by this hook for this specific evaluation. + Object value = data.get("span"); + if (value instanceof Span) { + Span span = (Span) value; + span.end(); + } + } +``` #### Requirement 4.3.1 +> `hook data` **MUST** be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. + +### 4.4. Hook creation and parameters + +#### Requirement 4.4.1 + > Hooks **MUST** specify at least one stage. -#### Condition 4.3.2 +#### Requirement 4.4.2 + +> `Hook data` **MUST** must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. + +Example showing data between `before` and `after` stage for two different hooks. +```mermaid +sequenceDiagram +actor Application +participant Client +participant HookA +participant HookB + +Application->>Client: getBooleanValue('my-bool', myContext, false) +activate Client + +Client-->>Client: create hook data for HookA + +Client->>HookA: before(hookContext, data: {}) +activate HookA + +HookA-->>HookA: data.set('key',' data for A') + +HookA-->>Client: (return) +deactivate HookA + +Client-->>Client: create hook data for HookB + +Client->>HookB: before(hookContext, hints, data: {}) +activate HookB + +HookB-->>HookB: data.set('key', 'data for B') +deactivate HookB + +Client-->>Client: Flag evaluation + +Client->>HookB: after(hookContext, detail, hints, data: {key: 'data for B'}) +activate HookB + +HookB-->>Client: (return) +deactivate HookB + +Client->>HookA: after(hookContext, data: {'key': 'data for A'}) +activate HookA + +HookA-->>Client: (return) +deactivate HookA + +Client-->>Application: true +deactivate Client + +``` + +#### Condition 4.4.2 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm) -##### Conditional Requirement 4.3.2.1 +##### Conditional Requirement 4.4.2.1 -> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters and returns either an `evaluation context` or nothing. +> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters and returns either an `evaluation context` or nothing. ```java -EvaluationContext | void before(HookContext hookContext, HookHints hints); +EvaluationContext | void before(HookContext hookContext, HookHints hints, HookData data); ``` -#### Condition 4.3.3 +#### Condition 4.4.3 [![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) @@ -109,47 +190,47 @@ EvaluationContext | void before(HookContext hookContext, HookHints hints); see: [static-context paradigm](../glossary.md#static-context-paradigm) -##### Conditional Requirement 4.3.3.1 +##### Conditional Requirement 4.4.3.1 -> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters. It has no return value. +> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters. It has no return value. ```java -void before(HookContext hookContext, HookHints hints); +void before(HookContext hookContext, HookHints hints, HookData data); ``` -#### Requirement 4.3.4 +#### Requirement 4.4.4 > Any `evaluation context` returned from a `before` hook **MUST** be passed to subsequent `before` hooks (via `HookContext`). -#### Requirement 4.3.5 +#### Requirement 4.4.5 > When `before` hooks have finished executing, any resulting `evaluation context` **MUST** be merged with the existing `evaluation context`. Evaluation context merge order is defined in [Context levels and merging](./03-evaluation-context.md#32-context-levels-and-merging). -#### Requirement 4.3.6 +#### Requirement 4.4.6 -> The `after` stage **MUST** run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required) and `hook hints` (optional). It has no return value. +> The `after` stage **MUST** run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required), `hook hints` (optional), and `hook data` (required). It has no return value. -#### Requirement 4.3.7 +#### Requirement 4.4.7 -> The `error` hook **MUST** run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), and `hook hints` (optional). It has no return value. +> The `error` hook **MUST** run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), `hook hints` (optional), and `hook data`. It has no return value. -#### Requirement 4.3.8 +#### Requirement 4.4.8 -> The `finally` hook **MUST** run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required) and `hook hints` (optional). There is no return value. +> The `finally` hook **MUST** run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required), `hook hints` (optional), and `hook data`. There is no return value. -#### Condition 4.3.9 +#### Condition 4.4.9 > `finally` is a reserved word in the language. -##### Conditional Requirement 4.3.9.1 +##### Conditional Requirement 4.4.9.1 > Instead of `finally`, `finallyAfter` **SHOULD** be used. -### 4.4. Hook registration & ordering +### 4.5. Hook registration & ordering -#### Requirement 4.4.1 +#### Requirement 4.5.1 > The API, Client, Provider, and invocation **MUST** have a method for registering hooks. @@ -166,7 +247,7 @@ client.addHooks(new Hook2()); client.getValue('my-flag', 'defaultValue', new Hook3()); ``` -#### Requirement 4.4.2 +#### Requirement 4.5.2 > Hooks **MUST** be evaluated in the following order: > @@ -175,27 +256,27 @@ client.getValue('my-flag', 'defaultValue', new Hook3()); > - error (if applicable): Provider, Invocation, Client, API > - finally: Provider, Invocation, Client, API -#### Requirement 4.4.3 +#### Requirement 4.5.3 > If a `finally` hook abnormally terminates, evaluation **MUST** proceed, including the execution of any remaining `finally` hooks. In languages with try/catch semantics, this means that exceptions thrown in `finally` hooks should be caught, and not propagated up the call stack. -#### Requirement 4.4.4 +#### Requirement 4.5.4 > If an `error` hook abnormally terminates, evaluation **MUST** proceed, including the execution of any remaining `error` hooks. In languages with try/catch semantics, this means that exceptions thrown in `error` hooks should be caught, and not propagated up the call stack. -#### Requirement 4.4.5 +#### Requirement 4.5.5 > If an error occurs in the `before` or `after` hooks, the `error` hooks **MUST** be invoked. -#### Requirement 4.4.6 +#### Requirement 4.5.6 > If an error occurs during the evaluation of `before` or `after` hooks, any remaining hooks in the `before` or `after` stages **MUST NOT** be invoked. -#### Requirement 4.4.7 +#### Requirement 4.5.7 > If an error occurs in the `before` hooks, the default value **MUST** be returned. @@ -214,14 +295,14 @@ val = client.get_boolean_value('my-key', False, evaluation_options={ see: [Flag evaluation options](./01-flag-evaluation.md#evaluation-options) -#### Requirement 4.5.1 +#### Requirement 4.6.1 > `Flag evaluation options` **MAY** contain `hook hints`, a map of data to be provided to hook invocations. -#### Requirement 4.5.2 +#### Requirement 4.6.2 > `hook hints` **MUST** be passed to each hook. -#### Requirement 4.5.3 +#### Requirement 4.6.3 > The hook **MUST NOT** alter the `hook hints` structure. From 2110b92f38edbfb86b79aaa829b1947bbedc53e9 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:00:06 -0700 Subject: [PATCH 02/10] Generate spec json. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification.json | 108 +++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 47 deletions(-) diff --git a/specification.json b/specification.json index 652c2c98..18a0ef4c 100644 --- a/specification.json +++ b/specification.json @@ -750,84 +750,98 @@ { "id": "Requirement 4.3.1", "machine_id": "requirement_4_3_1", + "content": "`hook data` MUST be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Requirement 4.4.1", + "machine_id": "requirement_4_4_1", "content": "Hooks MUST specify at least one stage.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Condition 4.3.2", - "machine_id": "condition_4_3_2", + "id": "Requirement 4.4.2", + "machine_id": "requirement_4_4_2", + "content": "`Hook data` MUST must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook.", + "RFC 2119 keyword": "MUST", + "children": [] + }, + { + "id": "Condition 4.4.2", + "machine_id": "condition_4_4_2", "content": "The implementation uses the dynamic-context paradigm.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 4.3.2.1", - "machine_id": "conditional_requirement_4_3_2_1", - "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters and returns either an `evaluation context` or nothing.", + "id": "Conditional Requirement 4.4.2.1", + "machine_id": "conditional_requirement_4_4_2_1", + "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters and returns either an `evaluation context` or nothing.", "RFC 2119 keyword": "MUST", "children": [] } ] }, { - "id": "Condition 4.3.3", - "machine_id": "condition_4_3_3", + "id": "Condition 4.4.3", + "machine_id": "condition_4_4_3", "content": "The implementation uses the static-context paradigm.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 4.3.3.1", - "machine_id": "conditional_requirement_4_3_3_1", - "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters. It has no return value.", + "id": "Conditional Requirement 4.4.3.1", + "machine_id": "conditional_requirement_4_4_3_1", + "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters. It has no return value.", "RFC 2119 keyword": "MUST", "children": [] } ] }, { - "id": "Requirement 4.3.4", - "machine_id": "requirement_4_3_4", + "id": "Requirement 4.4.4", + "machine_id": "requirement_4_4_4", "content": "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.3.5", - "machine_id": "requirement_4_3_5", + "id": "Requirement 4.4.5", + "machine_id": "requirement_4_4_5", "content": "When `before` hooks have finished executing, any resulting `evaluation context` MUST be merged with the existing `evaluation context`.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.3.6", - "machine_id": "requirement_4_3_6", - "content": "The `after` stage MUST run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required) and `hook hints` (optional). It has no return value.", + "id": "Requirement 4.4.6", + "machine_id": "requirement_4_4_6", + "content": "The `after` stage MUST run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required), `hook hints` (optional), and `hook data` (required). It has no return value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.3.7", - "machine_id": "requirement_4_3_7", - "content": "The `error` hook MUST run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), and `hook hints` (optional). It has no return value.", + "id": "Requirement 4.4.7", + "machine_id": "requirement_4_4_7", + "content": "The `error` hook MUST run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), `hook hints` (optional), and `hook data`. It has no return value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.3.8", - "machine_id": "requirement_4_3_8", - "content": "The `finally` hook MUST run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required) and `hook hints` (optional). There is no return value.", + "id": "Requirement 4.4.8", + "machine_id": "requirement_4_4_8", + "content": "The `finally` hook MUST run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required), `hook hints` (optional), and `hook data`. There is no return value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Condition 4.3.9", - "machine_id": "condition_4_3_9", + "id": "Condition 4.4.9", + "machine_id": "condition_4_4_9", "content": "`finally` is a reserved word in the language.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 4.3.9.1", - "machine_id": "conditional_requirement_4_3_9_1", + "id": "Conditional Requirement 4.4.9.1", + "machine_id": "conditional_requirement_4_4_9_1", "content": "Instead of `finally`, `finallyAfter` SHOULD be used.", "RFC 2119 keyword": "SHOULD", "children": [] @@ -835,71 +849,71 @@ ] }, { - "id": "Requirement 4.4.1", - "machine_id": "requirement_4_4_1", + "id": "Requirement 4.5.1", + "machine_id": "requirement_4_5_1", "content": "The API, Client, Provider, and invocation MUST have a method for registering hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.2", - "machine_id": "requirement_4_4_2", + "id": "Requirement 4.5.2", + "machine_id": "requirement_4_5_2", "content": "Hooks MUST be evaluated in the following order: - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.3", - "machine_id": "requirement_4_4_3", + "id": "Requirement 4.5.3", + "machine_id": "requirement_4_5_3", "content": "If a `finally` hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining `finally` hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.4", - "machine_id": "requirement_4_4_4", + "id": "Requirement 4.5.4", + "machine_id": "requirement_4_5_4", "content": "If an `error` hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining `error` hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.5", - "machine_id": "requirement_4_4_5", + "id": "Requirement 4.5.5", + "machine_id": "requirement_4_5_5", "content": "If an error occurs in the `before` or `after` hooks, the `error` hooks MUST be invoked.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.6", - "machine_id": "requirement_4_4_6", + "id": "Requirement 4.5.6", + "machine_id": "requirement_4_5_6", "content": "If an error occurs during the evaluation of `before` or `after` hooks, any remaining hooks in the `before` or `after` stages MUST NOT be invoked.", "RFC 2119 keyword": "MUST NOT", "children": [] }, { - "id": "Requirement 4.4.7", - "machine_id": "requirement_4_4_7", + "id": "Requirement 4.5.7", + "machine_id": "requirement_4_5_7", "content": "If an error occurs in the `before` hooks, the default value MUST be returned.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.1", - "machine_id": "requirement_4_5_1", + "id": "Requirement 4.6.1", + "machine_id": "requirement_4_6_1", "content": "`Flag evaluation options` MAY contain `hook hints`, a map of data to be provided to hook invocations.", "RFC 2119 keyword": "MAY", "children": [] }, { - "id": "Requirement 4.5.2", - "machine_id": "requirement_4_5_2", + "id": "Requirement 4.6.2", + "machine_id": "requirement_4_6_2", "content": "`hook hints` MUST be passed to each hook.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.3", - "machine_id": "requirement_4_5_3", + "id": "Requirement 4.6.3", + "machine_id": "requirement_4_6_3", "content": "The hook MUST NOT alter the `hook hints` structure.", "RFC 2119 keyword": "MUST NOT", "children": [] From e7a7bf4b3e1fe4852f754e22644dbaddec8627f3 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:00:51 -0700 Subject: [PATCH 03/10] Update specification/sections/04-hooks.md Co-authored-by: Michael Beemer Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification/sections/04-hooks.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index 5ee024f7..ffaa5b40 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -33,7 +33,11 @@ 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. +**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. ### 4.1. Hook context From 29bc48c4530ef25d26767624f71ff4aa0c69724b Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:22:42 -0700 Subject: [PATCH 04/10] PR feedback. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification/sections/04-hooks.md | 141 ++++++++++++++--------------- 1 file changed, 69 insertions(+), 72 deletions(-) diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index ffaa5b40..a562ccec 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -31,13 +31,7 @@ Hooks can be configured to run globally (impacting all flag evaluations), per cl ### Definitions -**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**: 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. ### 4.1. Hook context @@ -87,42 +81,15 @@ see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm) > Condition: The provider `metadata` field in the `hook context` **MUST** be immutable. -### 4.3. Hook data - -Hook data exists to allow hook stages to share data for a specific evaluation. For instance a span -for OpenTelemetry could be created in a `before` stage and closed in an `after` stage. - -```Java - public Optional before(HookContext context, HookHints hints, HookData data) { - SpanBuilder builder = tracer.spanBuilder('sample') - .setParent(Context.current().with(Span.current())); - Span span = builder.startSpan() - data.set("span", span); - } - - public void after(HookContext context, FlagEvaluationDetails details, HookHints hints, HookData data) { - // Only accessible by this hook for this specific evaluation. - Object value = data.get("span"); - if (value instanceof Span) { - Span span = (Span) value; - span.end(); - } - } -``` +### 4.3. Hook creation and parameters #### Requirement 4.3.1 -> `hook data` **MUST** be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. - -### 4.4. Hook creation and parameters - -#### Requirement 4.4.1 - > Hooks **MUST** specify at least one stage. -#### Requirement 4.4.2 +#### Requirement 4.3.2 -> `Hook data` **MUST** must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. +> `Hook data` **MUST** must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. The hook data is not shared between different hooks. Example showing data between `before` and `after` stage for two different hooks. ```mermaid @@ -137,31 +104,31 @@ activate Client Client-->>Client: create hook data for HookA -Client->>HookA: before(hookContext, data: {}) +Client->>HookA: before(hookContext: {data: {}, ... }) activate HookA -HookA-->>HookA: data.set('key',' data for A') +HookA-->>HookA: hookContext.hookData.set('key',' data for A') HookA-->>Client: (return) deactivate HookA Client-->>Client: create hook data for HookB -Client->>HookB: before(hookContext, hints, data: {}) +Client->>HookB: before(hookContext: {data: {}, ... }, hints) activate HookB -HookB-->>HookB: data.set('key', 'data for B') +HookB-->>HookB: hookContext.hookData.set('key', 'data for B') deactivate HookB Client-->>Client: Flag evaluation -Client->>HookB: after(hookContext, detail, hints, data: {key: 'data for B'}) +Client->>HookB: after(hookContext: {data: {key: 'data for B'}, ... }, detail, hints) activate HookB HookB-->>Client: (return) deactivate HookB -Client->>HookA: after(hookContext, data: {'key': 'data for A'}) +Client->>HookA: after(hookContext: {data: {'key': 'data for A'}, ... }) activate HookA HookA-->>Client: (return) @@ -172,21 +139,21 @@ deactivate Client ``` -#### Condition 4.4.2 +#### Condition 4.3.2 > The implementation uses the dynamic-context paradigm. see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm) -##### Conditional Requirement 4.4.2.1 +##### Conditional Requirement 4.3.2.1 -> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters and returns either an `evaluation context` or nothing. +> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters and returns either an `evaluation context` or nothing. ```java -EvaluationContext | void before(HookContext hookContext, HookHints hints, HookData data); +EvaluationContext | void before(HookContext hookContext, HookHints hints); ``` -#### Condition 4.4.3 +#### Condition 4.3.3 [![experimental](https://img.shields.io/static/v1?label=Status&message=experimental&color=orange)](https://github.com/open-feature/spec/tree/main/specification#experimental) @@ -194,47 +161,47 @@ EvaluationContext | void before(HookContext hookContext, HookHints hints, HookDa see: [static-context paradigm](../glossary.md#static-context-paradigm) -##### Conditional Requirement 4.4.3.1 +##### Conditional Requirement 4.3.3.1 -> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters. It has no return value. +> The `before` stage **MUST** run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters. It has no return value. ```java -void before(HookContext hookContext, HookHints hints, HookData data); +void before(HookContext hookContext, HookHints hints); ``` -#### Requirement 4.4.4 +#### Requirement 4.3.4 > Any `evaluation context` returned from a `before` hook **MUST** be passed to subsequent `before` hooks (via `HookContext`). -#### Requirement 4.4.5 +#### Requirement 4.3.5 > When `before` hooks have finished executing, any resulting `evaluation context` **MUST** be merged with the existing `evaluation context`. Evaluation context merge order is defined in [Context levels and merging](./03-evaluation-context.md#32-context-levels-and-merging). -#### Requirement 4.4.6 +#### Requirement 4.3.6 -> The `after` stage **MUST** run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required), `hook hints` (optional), and `hook data` (required). It has no return value. +> The `after` stage **MUST** run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required) and `hook hints` (optional). It has no return value. -#### Requirement 4.4.7 +#### Requirement 4.3.7 -> The `error` hook **MUST** run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), `hook hints` (optional), and `hook data`. It has no return value. +> The `error` hook **MUST** run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), and `hook hints` (optional). It has no return value. -#### Requirement 4.4.8 +#### Requirement 4.3.8 -> The `finally` hook **MUST** run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required), `hook hints` (optional), and `hook data`. There is no return value. +> The `finally` hook **MUST** run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required) and `hook hints` (optional). There is no return value. -#### Condition 4.4.9 +#### Condition 4.3.9 > `finally` is a reserved word in the language. -##### Conditional Requirement 4.4.9.1 +##### Conditional Requirement 4.3.9.1 > Instead of `finally`, `finallyAfter` **SHOULD** be used. -### 4.5. Hook registration & ordering +### 4.4. Hook registration & ordering -#### Requirement 4.5.1 +#### Requirement 4.4.1 > The API, Client, Provider, and invocation **MUST** have a method for registering hooks. @@ -251,7 +218,7 @@ client.addHooks(new Hook2()); client.getValue('my-flag', 'defaultValue', new Hook3()); ``` -#### Requirement 4.5.2 +#### Requirement 4.4.2 > Hooks **MUST** be evaluated in the following order: > @@ -260,27 +227,27 @@ client.getValue('my-flag', 'defaultValue', new Hook3()); > - error (if applicable): Provider, Invocation, Client, API > - finally: Provider, Invocation, Client, API -#### Requirement 4.5.3 +#### Requirement 4.4.3 > If a `finally` hook abnormally terminates, evaluation **MUST** proceed, including the execution of any remaining `finally` hooks. In languages with try/catch semantics, this means that exceptions thrown in `finally` hooks should be caught, and not propagated up the call stack. -#### Requirement 4.5.4 +#### Requirement 4.4.4 > If an `error` hook abnormally terminates, evaluation **MUST** proceed, including the execution of any remaining `error` hooks. In languages with try/catch semantics, this means that exceptions thrown in `error` hooks should be caught, and not propagated up the call stack. -#### Requirement 4.5.5 +#### Requirement 4.4.5 > If an error occurs in the `before` or `after` hooks, the `error` hooks **MUST** be invoked. -#### Requirement 4.5.6 +#### Requirement 4.4.6 > If an error occurs during the evaluation of `before` or `after` hooks, any remaining hooks in the `before` or `after` stages **MUST NOT** be invoked. -#### Requirement 4.5.7 +#### Requirement 4.4.7 > If an error occurs in the `before` hooks, the default value **MUST** be returned. @@ -299,14 +266,44 @@ val = client.get_boolean_value('my-key', False, evaluation_options={ see: [Flag evaluation options](./01-flag-evaluation.md#evaluation-options) -#### Requirement 4.6.1 +#### Requirement 4.5.1 > `Flag evaluation options` **MAY** contain `hook hints`, a map of data to be provided to hook invocations. -#### Requirement 4.6.2 +#### Requirement 4.5.2 > `hook hints` **MUST** be passed to each hook. -#### Requirement 4.6.3 +#### Requirement 4.5.3 > The hook **MUST NOT** alter the `hook hints` structure. + +### 4.6. Hook data + +Hook data exists to allow hook stages to share data for a specific evaluation. For instance a span +for OpenTelemetry could be created in a `before` stage and closed in an `after` stage. + +Hook data is scoped to a specific hook instance. The different stages of a hook share the same data, +but different hooks have different hook data instances. + +```Java + public Optional before(HookContext context, HookHints hints) { + SpanBuilder builder = tracer.spanBuilder('sample') + .setParent(Context.current().with(Span.current())); + Span span = builder.startSpan() + context.hookData.set("span", span); + } + + public void after(HookContext context, FlagEvaluationDetails details, HookHints hints) { + // Only accessible by this hook for this specific evaluation. + Object value = context.hookData.get("span"); + if (value instanceof Span) { + Span span = (Span) value; + span.end(); + } + } +``` + +#### Requirement4.6.1 + +> `hook data` **MUST** be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. From 0ef75245bac0c29018ab142175fdcedb967d4595 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:24:01 -0700 Subject: [PATCH 05/10] Generate spec. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification.json | 114 ++++++++++++++--------------- specification/sections/04-hooks.md | 2 +- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/specification.json b/specification.json index 18a0ef4c..2814a655 100644 --- a/specification.json +++ b/specification.json @@ -750,98 +750,91 @@ { "id": "Requirement 4.3.1", "machine_id": "requirement_4_3_1", - "content": "`hook data` MUST be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.", - "RFC 2119 keyword": "MUST", - "children": [] - }, - { - "id": "Requirement 4.4.1", - "machine_id": "requirement_4_4_1", "content": "Hooks MUST specify at least one stage.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.2", - "machine_id": "requirement_4_4_2", - "content": "`Hook data` MUST must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook.", + "id": "Requirement 4.3.2", + "machine_id": "requirement_4_3_2", + "content": "`Hook data` MUST must be created before the first `stage` invoked in a hook for a specific evaluation and propagated between each `stage` of the hook. The hook data is not shared between different hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Condition 4.4.2", - "machine_id": "condition_4_4_2", + "id": "Condition 4.3.2", + "machine_id": "condition_4_3_2", "content": "The implementation uses the dynamic-context paradigm.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 4.4.2.1", - "machine_id": "conditional_requirement_4_4_2_1", - "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters and returns either an `evaluation context` or nothing.", + "id": "Conditional Requirement 4.3.2.1", + "machine_id": "conditional_requirement_4_3_2_1", + "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters and returns either an `evaluation context` or nothing.", "RFC 2119 keyword": "MUST", "children": [] } ] }, { - "id": "Condition 4.4.3", - "machine_id": "condition_4_4_3", + "id": "Condition 4.3.3", + "machine_id": "condition_4_3_3", "content": "The implementation uses the static-context paradigm.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 4.4.3.1", - "machine_id": "conditional_requirement_4_4_3_1", - "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required), `hook hints` (optional), and `hook data` (required) as parameters. It has no return value.", + "id": "Conditional Requirement 4.3.3.1", + "machine_id": "conditional_requirement_4_3_3_1", + "content": "The `before` stage MUST run before flag resolution occurs. It accepts a `hook context` (required) and `hook hints` (optional) as parameters. It has no return value.", "RFC 2119 keyword": "MUST", "children": [] } ] }, { - "id": "Requirement 4.4.4", - "machine_id": "requirement_4_4_4", + "id": "Requirement 4.3.4", + "machine_id": "requirement_4_3_4", "content": "Any `evaluation context` returned from a `before` hook MUST be passed to subsequent `before` hooks (via `HookContext`).", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.5", - "machine_id": "requirement_4_4_5", + "id": "Requirement 4.3.5", + "machine_id": "requirement_4_3_5", "content": "When `before` hooks have finished executing, any resulting `evaluation context` MUST be merged with the existing `evaluation context`.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.6", - "machine_id": "requirement_4_4_6", - "content": "The `after` stage MUST run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required), `hook hints` (optional), and `hook data` (required). It has no return value.", + "id": "Requirement 4.3.6", + "machine_id": "requirement_4_3_6", + "content": "The `after` stage MUST run after flag resolution occurs. It accepts a `hook context` (required), `flag evaluation details` (required) and `hook hints` (optional). It has no return value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.7", - "machine_id": "requirement_4_4_7", - "content": "The `error` hook MUST run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), `hook hints` (optional), and `hook data`. It has no return value.", + "id": "Requirement 4.3.7", + "machine_id": "requirement_4_3_7", + "content": "The `error` hook MUST run when errors are encountered in the `before` stage, the `after` stage or during flag resolution. It accepts `hook context` (required), `exception` representing what went wrong (required), and `hook hints` (optional). It has no return value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.4.8", - "machine_id": "requirement_4_4_8", - "content": "The `finally` hook MUST run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required), `hook hints` (optional), and `hook data`. There is no return value.", + "id": "Requirement 4.3.8", + "machine_id": "requirement_4_3_8", + "content": "The `finally` hook MUST run after the `before`, `after`, and `error` stages. It accepts a `hook context` (required) and `hook hints` (optional). There is no return value.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Condition 4.4.9", - "machine_id": "condition_4_4_9", + "id": "Condition 4.3.9", + "machine_id": "condition_4_3_9", "content": "`finally` is a reserved word in the language.", "RFC 2119 keyword": null, "children": [ { - "id": "Conditional Requirement 4.4.9.1", - "machine_id": "conditional_requirement_4_4_9_1", + "id": "Conditional Requirement 4.3.9.1", + "machine_id": "conditional_requirement_4_3_9_1", "content": "Instead of `finally`, `finallyAfter` SHOULD be used.", "RFC 2119 keyword": "SHOULD", "children": [] @@ -849,75 +842,82 @@ ] }, { - "id": "Requirement 4.5.1", - "machine_id": "requirement_4_5_1", + "id": "Requirement 4.4.1", + "machine_id": "requirement_4_4_1", "content": "The API, Client, Provider, and invocation MUST have a method for registering hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.2", - "machine_id": "requirement_4_5_2", + "id": "Requirement 4.4.2", + "machine_id": "requirement_4_4_2", "content": "Hooks MUST be evaluated in the following order: - before: API, Client, Invocation, Provider - after: Provider, Invocation, Client, API - error (if applicable): Provider, Invocation, Client, API - finally: Provider, Invocation, Client, API", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.3", - "machine_id": "requirement_4_5_3", + "id": "Requirement 4.4.3", + "machine_id": "requirement_4_4_3", "content": "If a `finally` hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining `finally` hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.4", - "machine_id": "requirement_4_5_4", + "id": "Requirement 4.4.4", + "machine_id": "requirement_4_4_4", "content": "If an `error` hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining `error` hooks.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.5", - "machine_id": "requirement_4_5_5", + "id": "Requirement 4.4.5", + "machine_id": "requirement_4_4_5", "content": "If an error occurs in the `before` or `after` hooks, the `error` hooks MUST be invoked.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.5.6", - "machine_id": "requirement_4_5_6", + "id": "Requirement 4.4.6", + "machine_id": "requirement_4_4_6", "content": "If an error occurs during the evaluation of `before` or `after` hooks, any remaining hooks in the `before` or `after` stages MUST NOT be invoked.", "RFC 2119 keyword": "MUST NOT", "children": [] }, { - "id": "Requirement 4.5.7", - "machine_id": "requirement_4_5_7", + "id": "Requirement 4.4.7", + "machine_id": "requirement_4_4_7", "content": "If an error occurs in the `before` hooks, the default value MUST be returned.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.6.1", - "machine_id": "requirement_4_6_1", + "id": "Requirement 4.5.1", + "machine_id": "requirement_4_5_1", "content": "`Flag evaluation options` MAY contain `hook hints`, a map of data to be provided to hook invocations.", "RFC 2119 keyword": "MAY", "children": [] }, { - "id": "Requirement 4.6.2", - "machine_id": "requirement_4_6_2", + "id": "Requirement 4.5.2", + "machine_id": "requirement_4_5_2", "content": "`hook hints` MUST be passed to each hook.", "RFC 2119 keyword": "MUST", "children": [] }, { - "id": "Requirement 4.6.3", - "machine_id": "requirement_4_6_3", + "id": "Requirement 4.5.3", + "machine_id": "requirement_4_5_3", "content": "The hook MUST NOT alter the `hook hints` structure.", "RFC 2119 keyword": "MUST NOT", "children": [] }, + { + "id": "Requirement 4.6.1", + "machine_id": "requirement_4_6_1", + "content": "`hook data` MUST be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.", + "RFC 2119 keyword": "MUST", + "children": [] + }, { "id": "Requirement 5.1.1", "machine_id": "requirement_5_1_1", diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index a562ccec..c02232ae 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -304,6 +304,6 @@ but different hooks have different hook data instances. } ``` -#### Requirement4.6.1 +#### Requirement 4.6.1 > `hook data` **MUST** be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. From 8d732ac521d3c3feedccf1891bc074d334da0bf4 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:28:06 -0700 Subject: [PATCH 06/10] Update hook context section. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification/sections/04-hooks.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index c02232ae..5c5cbcdb 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -35,11 +35,11 @@ Hooks can be configured to run globally (impacting all flag evaluations), per cl ### 4.1. Hook context -Hook context exists to provide hooks with information about the invocation. +Hook context exists to provide hooks with information about the invocation and propagate data between hook stages. #### Requirement 4.1.1 -> Hook context **MUST** provide: the `flag key`, `flag value type`, `evaluation context`, and the `default value`. +> Hook context **MUST** provide: the `flag key`, `flag value type`, `evaluation context`, `default value`, and `hook data`. #### Requirement 4.1.2 @@ -59,6 +59,22 @@ see: [dynamic-context paradigm](../glossary.md#dynamic-context-paradigm) > The evaluation context **MUST** be mutable only within the `before` hook. +#### Requirement 4.1.5 + +> The `hook data` **MUST** be mutable. + +Either the `hook data` reference itself must be mutable, or it must allow mutation of its contents. + +Mutable reference: +``` +hookContext.hookData = {'my-key': 'my-value'} +``` + +Mutable content: +``` +hookContext.hookData.set('my-key', 'my-value') +``` + ### 4.2. Hook Hints #### Requirement 4.2.1 From 7591a03bbb7205eed38b33ec24eac20102f1b842 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:28:34 -0700 Subject: [PATCH 07/10] Make spec. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/specification.json b/specification.json index 2814a655..39b3a858 100644 --- a/specification.json +++ b/specification.json @@ -678,7 +678,7 @@ { "id": "Requirement 4.1.1", "machine_id": "requirement_4_1_1", - "content": "Hook context MUST provide: the `flag key`, `flag value type`, `evaluation context`, and the `default value`.", + "content": "Hook context MUST provide: the `flag key`, `flag value type`, `evaluation context`, `default value`, and `hook data`.", "RFC 2119 keyword": "MUST", "children": [] }, @@ -711,6 +711,13 @@ } ] }, + { + "id": "Requirement 4.1.5", + "machine_id": "requirement_4_1_5", + "content": "The `hook data` MUST be mutable.", + "RFC 2119 keyword": "MUST", + "children": [] + }, { "id": "Requirement 4.2.1", "machine_id": "requirement_4_2_1", From 8157ad9ac24d22676dc69067a378785dfcd304d9 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:30:49 -0700 Subject: [PATCH 08/10] Re-fix definitions. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification/sections/04-hooks.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index 5c5cbcdb..310294bf 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -31,7 +31,13 @@ Hooks can be configured to run globally (impacting all flag evaluations), per cl ### Definitions -**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**: 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. ### 4.1. Hook context From d869ae4d67b39438cb05e1588c5c516ffe97bac7 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:51:27 -0800 Subject: [PATCH 09/10] Update specification/sections/04-hooks.md Co-authored-by: Lukas Reining Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification/sections/04-hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/sections/04-hooks.md b/specification/sections/04-hooks.md index 310294bf..ada6001f 100644 --- a/specification/sections/04-hooks.md +++ b/specification/sections/04-hooks.md @@ -328,4 +328,4 @@ but different hooks have different hook data instances. #### Requirement 4.6.1 -> `hook data` **MUST** be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. +> `hook data` **MUST** be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`. From 2b3acfd9674fac48fc5a23ce6eb1cf3c9a934da0 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:54:22 -0800 Subject: [PATCH 10/10] Regenerate spec JSON. Signed-off-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> --- specification.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification.json b/specification.json index a56da251..2c9c2858 100644 --- a/specification.json +++ b/specification.json @@ -928,7 +928,7 @@ { "id": "Requirement 4.6.1", "machine_id": "requirement_4_6_1", - "content": "`hook data` MUST be a structure supports definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.", + "content": "`hook data` MUST be a structure supporting the definition of arbitrary properties, with keys of type `string`, and values of type `boolean | string | number | datetime | structure`.", "RFC 2119 keyword": "MUST", "children": [] },