Repository: open-feature/go-sdk
Pull request #188 (2023-06-20 21:29:50)

feat: eventing implementation
## This PR

Covers the initial implementation of https://github.com/open-feature/go-sdk/issues/186

⚠️  Note that, this implementation does not cover the 1 to N relationship requirement of provider to named client requirement (i.e - single instance of a provider can be bound to multiple names, thus allowing that single provider to be bound by multiple clients). A follow-up PR is ready  for this - https://github.com/open-feature/go-sdk/pull/190 


This PR introduces OpenFeature eventing spec [1] to Go SDK. The implementation is non-breaking and providers which plan to support eventing can opt-in by implementing `EventHandler` interface.

### Implementation 

`EventHandler` interface contains a single method `EventChannel() <- chan Event`. OpenFeature core implementation performs a type assertion on the provider for `EventHandler` and listen to `Event` channel. It is provider implementation responsibility to emit spec-compliant events (ex:- ProviderReady when initialization is complete OR ProviderError when initialization failed)

The core event handling logic is concentrated on `event_executor.go`. OpenFeature core implementation maintains a single `EventExecutor` instance that handles both global (api) level events and client level (scoped) events.

Client and API both add and remove event handlers (callbacks) through their spec client accessors and these get stored at the global `EventExecutor`

```mermaid
flowchart TD
    A[OpenFeature] -->|Register Global Handler|D
    D[API Singleton] -->  B(EventExecutor)
    C[Client Instance] -->|Register Client Handler|D

```

When handling events from providers, all global-level handlers are always invoked. But, the client (scoped) handlers only run if they are associated with the specific provider. This is done by comparing event payload's provider name & registered client name of the handler

```mermaid
sequenceDiagram
    Provider->>+EventExecutor: Emit Event: ProviderReady
    EventExecutor-->>EventExecutor: Run Global Handlers
    EventExecutor-->>EventExecutor: Run Client Handlers - filter with provider name
```

### Testing 

Consider checking `event_executor_test.go` [2] to validate spec compliance as well as the correctness of the implementation 



[1] - https://github.com/open-feature/spec/blob/main/specification/sections/05-events.md#5-events
[2] - https://github.com/open-feature/go-sdk/blob/fe93b46b4afc11533b0530314cc71a823abfe705/pkg/openfeature/event_executor_test.go

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) Report
> Merging [#188](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) (7698dd0) into [main](https://app.codecov.io/gh/open-feature/go-sdk/commit/609af46881a371dd1b18ce065164b9f116acdb7f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) (609af46) will **increase** coverage by `3.33%`.
> The diff coverage is `88.88%`.

```diff
@@            Coverage Diff             @@
##             main     #188      +/-   ##
==========================================
+ Coverage   72.97%   76.31%   +3.33%     
==========================================
  Files           8        9       +1     
  Lines         766      971     +205     
==========================================
+ Hits          559      741     +182     
- Misses        187      207      +20     
- Partials       20       23       +3     
```


| [Impacted Files](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | Coverage Δ | |
|---|---|---|
| [pkg/openfeature/provider.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL3Byb3ZpZGVyLmdv) | `68.00% <0.00%> (-32.00%)` | :arrow_down: |
| [pkg/openfeature/api.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL2FwaS5nbw==) | `90.35% <66.66%> (-4.55%)` | :arrow_down: |
| [pkg/openfeature/openfeature.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL29wZW5mZWF0dXJlLmdv) | `97.67% <93.33%> (-2.33%)` | :arrow_down: |
| [pkg/openfeature/event\_handler.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL2V2ZW50X2hhbmRsZXIuZ28=) | `95.06% <95.06%> (ø)` | |
| [pkg/openfeature/client.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL2NsaWVudC5nbw==) | `70.44% <100.00%> (+0.24%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature)

Kavindu-Dodan: > Looks good to me overall, with a few nits (assuming the 1:N issue is fixed elsewhere).

correct, 1:N support was missing in this implementation and I am actively working on enabling this. 

Patch:
diff --git a/go.mod b/go.mod
index 4ac1ed1..aba1f18 100644
--- a/go.mod
+++ b/go.mod
@@ -6,53 +6,48 @@ require (
 	github.com/cucumber/godog v0.12.6
 	github.com/go-logr/logr v1.2.4
 	github.com/golang/mock v1.6.0
-	github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.13
+	github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.7
 	github.com/open-feature/go-sdk-contrib/tests/flagd v1.1.0
+	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
 	golang.org/x/text v0.11.0
 )
 
 require (
-	buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.5.2-20230317150644-afd1cc2ef580.1 // indirect
-	buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.29.1-20230317150644-afd1cc2ef580.1 // indirect
+	buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.4.1-20221226184428-0dc62ff103b8.1 // indirect
+	buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20221226184428-0dc62ff103b8.4 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/bufbuild/connect-go v1.8.0 // indirect
-	github.com/bufbuild/connect-opentelemetry-go v0.3.0 // indirect
+	github.com/bufbuild/connect-go v1.5.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
 	github.com/cucumber/messages-go/v16 v16.0.1 // indirect
 	github.com/diegoholiveira/jsonlogic/v3 v3.2.7 // indirect
-	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/gofrs/uuid v4.2.0+incompatible // indirect
-	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
 	github.com/hashicorp/go-memdb v1.3.2 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
-	github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
+	github.com/klauspost/cpuid/v2 v2.1.1 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/open-feature/flagd v0.3.7-0.20230210161723-e906757a4bac // indirect
-	github.com/open-feature/flagd/core v0.5.3 // indirect
 	github.com/open-feature/schemas v0.2.8 // indirect
-	github.com/prometheus/client_golang v1.15.0 // indirect
+	github.com/prometheus/client_golang v1.14.0 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect
-	github.com/prometheus/common v0.42.0 // indirect
-	github.com/prometheus/procfs v0.9.0 // indirect
-	github.com/rs/cors v1.9.0 // indirect
-	github.com/rs/xid v1.5.0 // indirect
+	github.com/prometheus/common v0.37.0 // indirect
+	github.com/prometheus/procfs v0.8.0 // indirect
+	github.com/rs/cors v1.8.3 // indirect
+	github.com/rs/xid v1.4.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/zeebo/xxh3 v1.0.2 // indirect
-	go.opentelemetry.io/otel v1.16.0 // indirect
-	go.opentelemetry.io/otel/metric v1.16.0 // indirect
-	go.opentelemetry.io/otel/trace v1.16.0 // indirect
 	go.uber.org/atomic v1.10.0 // indirect
-	go.uber.org/multierr v1.9.0 // indirect
+	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.24.0 // indirect
-	golang.org/x/net v0.10.0 // indirect
-	golang.org/x/sys v0.8.0 // indirect
-	google.golang.org/protobuf v1.30.0 // indirect
+	golang.org/x/net v0.6.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 )
diff --git a/go.sum b/go.sum
index b5ed975..e7454d5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,9 @@
 buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20220906183531-bc28b723cd77.4/go.mod h1:92ejKVTiuvnKoAtRlpJpIxKfloI935DDqhs0NCRx+KM=
-buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.29.1-20220906183531-bc28b723cd77.1/go.mod h1:hhON98gyTBA8hmdzw7bPux2Zerm5Sp2hesfc5t6jpoI=
 buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.4.1-20221226184428-0dc62ff103b8.1 h1:KoSPqmHyi3x27tPFLQ994CJjG4qc59v+0gbxY9+VXso=
 buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.4.1-20221226184428-0dc62ff103b8.1/go.mod h1:68WGv4z/jXuTS3G7FEFQTEw4wiMmulBSX6BlSFX2Xc8=
-buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.5.2-20230317150644-afd1cc2ef580.1 h1:x96ZMVfnpeSSpOOoVXyoeWuP793N1ruQBPuiQ33QTuQ=
-buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.5.2-20230317150644-afd1cc2ef580.1/go.mod h1:7M356i3wVPfGd2Ub1PZH8MybyTXRmy8HI1jc24cBAjI=
 buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20221226184428-0dc62ff103b8.4 h1:9ioWUVmnURL+TX4qVjyzzE3o9Z2ACDGidqtu9dkjmdY=
 buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20221226184428-0dc62ff103b8.4 h1:HMDOJt1SDrELWslNJWfjg4ItbNOPx3ZvD8RtikKr5SE=
 buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20221226184428-0dc62ff103b8.4/go.mod h1:+Bnrjo56uVn/aBcLWchTveR8UeCj+KSJN4fE0xSmBNc=
-buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.29.1-20230317150644-afd1cc2ef580.1 h1:CWgXoLTajc7QagTz76kplcFnjjWIkxhhDdmKYtVShOU=
-buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.29.1-20230317150644-afd1cc2ef580.1/go.mod h1:WWnccsJMW7QwvXkdXUuQmg28sx+ZoX36+J2TAjcy6u4=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -56,13 +51,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bufbuild/connect-go v1.5.1 h1:ORhrSiu63hWxtuMmC/V1mKySSRhEySsW5RkHJcyJXBk=
 github.com/bufbuild/connect-go v1.5.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I=
-github.com/bufbuild/connect-go v1.8.0 h1:srluNkFkZBfSfg9Qb6DrO+5nMaxix//h2ctrHZhMGKc=
-github.com/bufbuild/connect-go v1.8.0/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk=
-github.com/bufbuild/connect-opentelemetry-go v0.3.0 h1:AuZi3asTDKmjGtd2aqpyP4p5QvBFG/YEaHopViLatnk=
-github.com/bufbuild/connect-opentelemetry-go v0.3.0/go.mod h1:r1ppyTtu1EWeRodk4Q/JbyQhIWtO7eR3GoRDzjeEcNU=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -99,13 +89,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
-github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
 github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
@@ -141,7 +126,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -183,8 +167,6 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
 github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU=
-github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -201,8 +183,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
 github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
-github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
-github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -226,18 +206,10 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/open-feature/flagd v0.3.4 h1:EeEzMgRGzyyrpsvk3fZgK1dDa8+7zxLBJV5fXii6si0=
-github.com/open-feature/flagd v0.3.4/go.mod h1:JOIpaTkqYfAoUCCbVdSGoxKTJr2SV4FGuKmGjRfaQq8=
 github.com/open-feature/flagd v0.3.7-0.20230210161723-e906757a4bac h1:OWj8wUHgkap4Bq5v8UAF65Wvdnils+WQPLFaOda7r1U=
 github.com/open-feature/flagd v0.3.7-0.20230210161723-e906757a4bac/go.mod h1:IQ/mfi0P0dL+4m9X9prbHc64fthbimr1CdZWppHcf70=
-github.com/open-feature/flagd/core v0.5.3 h1:ztWPkLs2mRk4dHvUYLc7bsGyght43fnvomjc+pe98t4=
-github.com/open-feature/flagd/core v0.5.3/go.mod h1:pVS/k/iEaJ5fPeIND8JpB22mqCi3iDcgb8wEgiqipnM=
 github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.7 h1:0s8reX/EfCNV37PsGSr55wUpppPtyp0jZKeuVAaWZ+4=
 github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.7/go.mod h1:dHB0hsYykZ1Un+CdnWErqLqUQswUADIvDg2VwDLx7gs=
-github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.13 h1:vw9JzZInncUmhnozKoQsq/CrskROtU2K8TIgZZ69NLE=
-github.com/open-feature/go-sdk-contrib/providers/flagd v0.1.13/go.mod h1:AuVhEbeSlK1SFMFezq5eafTv67r10udiarTgaA3ekow=
-github.com/open-feature/go-sdk-contrib/tests/flagd v1.0.1 h1:FATKM2T7O4OBeVF7pjJldwegbGm1JXhX11f5ffldbFA=
-github.com/open-feature/go-sdk-contrib/tests/flagd v1.0.1/go.mod h1:Oo6p7XjTcMHbiWrU/UwxN6Uguo0lHGfMsTYksEs/cKE=
 github.com/open-feature/go-sdk-contrib/tests/flagd v1.1.0 h1:lM+7hfFVwUxQPOyJ9rXn2UR4sCAfOQJKtCU29pU/B+4=
 github.com/open-feature/go-sdk-contrib/tests/flagd v1.1.0/go.mod h1:zw/xpuDy9ziBEKVA1t4VoQtzFc80btAAQCiZkX6y9oQ=
 github.com/open-feature/schemas v0.2.8 h1:oA75hJXpOd9SFgmNI2IAxWZkwzQPUDm7Jyyh3q489wM=
@@ -255,7 +227,6 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
 github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
-github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -268,7 +239,6 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
 github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
-github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@@ -276,14 +246,11 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
-github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
 github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
-github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
 github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -323,20 +290,12 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
-go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
-go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
-go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
-go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
-go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
 go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
 go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
 go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
 go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
 go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -355,6 +314,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -408,12 +369,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -472,12 +429,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -487,14 +440,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
-golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
 golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -597,7 +542,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
+google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -610,7 +555,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
+google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -625,9 +570,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/openfeature/api.go b/pkg/openfeature/api.go
index 7daf7db..d58da70 100644
--- a/pkg/openfeature/api.go
+++ b/pkg/openfeature/api.go
@@ -16,17 +16,21 @@ type evaluationAPI struct {
 	evalCtx         EvaluationContext
 	logger          logr.Logger
 	mu              sync.RWMutex
+	eventExecutor
 }
 
 // newEvaluationAPI is a helper to generate an API. Used internally
 func newEvaluationAPI() evaluationAPI {
+	logger := logr.New(internal.Logger{})
+
 	return evaluationAPI{
 		defaultProvider: NoopProvider{},
 		namedProviders:  map[string]FeatureProvider{},
 		hks:             []Hook{},
 		evalCtx:         EvaluationContext{},
-		logger:          logr.New(internal.Logger{}),
+		logger:          logger,
 		mu:              sync.RWMutex{},
+		eventExecutor:   newEventExecutor(logger),
 	}
 }
 
@@ -41,9 +45,15 @@ func (api *evaluationAPI) setProvider(provider FeatureProvider) error {
 
 	// Initialize new default provider and shutdown the old one
 	// Provider update must be non-blocking, hence initialization & shutdown happens concurrently
-	api.initAndShutdown(provider, api.defaultProvider)
+	oldProvider := api.defaultProvider
+	api.initAndShutdown(provider, oldProvider)
 	api.defaultProvider = provider
 
+	err := api.registerDefaultProvider(provider)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -69,6 +79,11 @@ func (api *evaluationAPI) setNamedProvider(clientName string, provider FeaturePr
 	api.initAndShutdown(provider, api.namedProviders[clientName])
 	api.namedProviders[clientName] = provider
 
+	err := api.registerNamedEventingProvider(clientName, provider)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -92,6 +107,7 @@ func (api *evaluationAPI) setLogger(l logr.Logger) {
 	defer api.mu.Unlock()
 
 	api.logger = l
+	api.eventExecutor.updateLogger(l)
 }
 
 func (api *evaluationAPI) getLogger() logr.Logger {
diff --git a/pkg/openfeature/client.go b/pkg/openfeature/client.go
index 91e0b24..79b8c35 100644
--- a/pkg/openfeature/client.go
+++ b/pkg/openfeature/client.go
@@ -14,6 +14,8 @@ import (
 type IClient interface {
 	Metadata() ClientMetadata
 	AddHooks(hooks ...Hook)
+	AddHandler(eventType EventType, callback EventCallback)
+	RemoveHandler(eventType EventType, callback EventCallback)
 	SetEvaluationContext(evalCtx EvaluationContext)
 	EvaluationContext() EvaluationContext
 	BooleanValue(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) (bool, error)
@@ -87,6 +89,16 @@ func (c *Client) AddHooks(hooks ...Hook) {
 	c.hooks = append(c.hooks, hooks...)
 }
 
+// AddHandler allows to add Client level event handler
+func (c *Client) AddHandler(eventType EventType, callback EventCallback) {
+	addClientHandler(c.metadata.Name(), eventType, callback)
+}
+
+// RemoveHandler allows to remove Client level event handler
+func (c *Client) RemoveHandler(eventType EventType, callback EventCallback) {
+	removeClientHandler(c.metadata.Name(), eventType, callback)
+}
+
 // SetEvaluationContext sets the client's evaluation context
 func (c *Client) SetEvaluationContext(evalCtx EvaluationContext) {
 	c.mx.Lock()
@@ -744,6 +756,7 @@ func (c *Client) evaluate(
 		c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options)
 		return evalDetails, err
 	}
+
 	return evalDetails, nil
 }
 
diff --git a/pkg/openfeature/event_executor.go b/pkg/openfeature/event_executor.go
new file mode 100644
index 0000000..66ae342
--- /dev/null
+++ b/pkg/openfeature/event_executor.go
@@ -0,0 +1,296 @@
+package openfeature
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/go-logr/logr"
+)
+
+// event executor is a registry to connect API and Client event handlers to Providers
+
+type eventExecutor struct {
+	defaultProviderReference *providerReference
+	namedProviderReference   map[string]*providerReference
+	apiRegistry              map[EventType][]EventCallback
+	scopedRegistry           map[string]scopedCallback
+	logger                   logr.Logger
+	mu                       sync.Mutex
+}
+
+func newEventExecutor(logger logr.Logger) eventExecutor {
+	return eventExecutor{
+		namedProviderReference: map[string]*providerReference{},
+		apiRegistry:            map[EventType][]EventCallback{},
+		scopedRegistry:         map[string]scopedCallback{},
+		logger:                 logger,
+	}
+}
+
+// scopedCallback is a helper struct to hold client name associated callbacks.
+// Here, the scope correlates to the client and provider name
+type scopedCallback struct {
+	scope     string
+	callbacks map[EventType][]EventCallback
+}
+
+func newScopedCallback(client string) scopedCallback {
+	return scopedCallback{
+		scope:     client,
+		callbacks: map[EventType][]EventCallback{},
+	}
+}
+
+type providerReference struct {
+	eventHandler          *EventHandler
+	metadata              Metadata
+	clientNameAssociation string
+	isDefault             bool
+	shutdownSemaphore     chan interface{}
+}
+
+// updateLogger updates the executor's logger
+func (e *eventExecutor) updateLogger(l logr.Logger) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	e.logger = l
+}
+
+// registerApiHandler adds an API(global) level handler
+func (e *eventExecutor) registerApiHandler(t EventType, c EventCallback) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	if e.apiRegistry[t] == nil {
+		e.apiRegistry[t] = []EventCallback{c}
+	} else {
+		e.apiRegistry[t] = append(e.apiRegistry[t], c)
+	}
+}
+
+// removeApiHandler removes an API(global) level handler
+func (e *eventExecutor) removeApiHandler(t EventType, c EventCallback) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	entrySlice, ok := e.apiRegistry[t]
+	if !ok {
+		// nothing to remove
+		return
+	}
+
+	for i, f := range entrySlice {
+		if f == c {
+			entrySlice = append(entrySlice[:i], entrySlice[i+1:]...)
+		}
+	}
+
+	e.apiRegistry[t] = entrySlice
+}
+
+// registerClientHandler registers a client level handler
+func (e *eventExecutor) registerClientHandler(clientName string, t EventType, c EventCallback) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	_, ok := e.scopedRegistry[clientName]
+	if !ok {
+		e.scopedRegistry[clientName] = newScopedCallback(clientName)
+	}
+
+	registry := e.scopedRegistry[clientName]
+
+	if registry.callbacks[t] == nil {
+		registry.callbacks[t] = []EventCallback{c}
+	} else {
+		registry.callbacks[t] = append(registry.callbacks[t], c)
+	}
+
+	// fulfil spec requirement to fire ready events if associated provider is ready
+	if t != ProviderReady {
+		return
+	}
+
+	provider, ok := e.namedProviderReference[clientName]
+	if !ok {
+		return
+	}
+
+	s, ok := (*provider.eventHandler).(StateHandler)
+	if !ok {
+		return
+	}
+
+	if s.Status() == ReadyState {
+		(*c)(EventDetails{
+			providerName: provider.metadata.Name,
+			ProviderEventDetails: ProviderEventDetails{
+				Message: "provider is at ready state",
+			},
+		})
+	}
+}
+
+// removeClientHandler removes a client level handler
+func (e *eventExecutor) removeClientHandler(name string, t EventType, c EventCallback) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	_, ok := e.scopedRegistry[name]
+	if !ok {
+		// nothing to remove
+		return
+	}
+
+	entrySlice := e.scopedRegistry[name].callbacks[t]
+	if entrySlice == nil {
+		// nothing to remove
+		return
+	}
+
+	for i, f := range entrySlice {
+		if f == c {
+			entrySlice = append(entrySlice[:i], entrySlice[i+1:]...)
+		}
+	}
+
+	e.scopedRegistry[name].callbacks[t] = entrySlice
+}
+
+// registerDefaultProvider registers the default FeatureProvider and remove the old default provider if available
+func (e *eventExecutor) registerDefaultProvider(provider FeatureProvider) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	v, ok := provider.(EventHandler)
+	if !ok {
+		return nil
+	}
+
+	oldProvider := e.defaultProviderReference
+
+	// register shutdown semaphore for new default provider
+	sem := make(chan interface{})
+
+	newProvider := &providerReference{
+		eventHandler:      &v,
+		metadata:          provider.Metadata(),
+		isDefault:         true,
+		shutdownSemaphore: sem,
+	}
+
+	e.defaultProviderReference = newProvider
+	return e.listenAndShutdown(newProvider, oldProvider)
+}
+
+// registerNamedEventingProvider registers a named FeatureProvider and remove event listener for old named provider
+func (e *eventExecutor) registerNamedEventingProvider(associatedClient string, provider FeatureProvider) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	v, ok := provider.(EventHandler)
+	if !ok {
+		return nil
+	}
+
+	oldProvider := e.namedProviderReference[associatedClient]
+
+	// register shutdown semaphore for new named provider
+	sem := make(chan interface{})
+
+	newProvider := &providerReference{
+		eventHandler:          &v,
+		clientNameAssociation: associatedClient,
+		metadata:              provider.Metadata(),
+		shutdownSemaphore:     sem,
+	}
+
+	e.namedProviderReference[associatedClient] = newProvider
+	return e.listenAndShutdown(newProvider, oldProvider)
+}
+
+// listenAndShutdown is a helper to start concurrent listening to new provider events and invoke shutdown hook of the
+// old provider
+func (e *eventExecutor) listenAndShutdown(newProvider *providerReference, oldReference *providerReference) error {
+	go func(newProvider *providerReference, oldReference *providerReference) {
+		for {
+			select {
+			case event := <-(*newProvider.eventHandler).EventChannel():
+				e.triggerEvent(event, newProvider.clientNameAssociation, newProvider.isDefault)
+			case <-newProvider.shutdownSemaphore:
+				return
+			}
+		}
+	}(newProvider, oldReference)
+
+	// shutdown old provider handling
+	if oldReference == nil {
+		return nil
+	}
+
+	select {
+	case oldReference.shutdownSemaphore <- "":
+		return nil
+	case <-time.After(200 * time.Millisecond):
+		return fmt.Errorf("event handler timeout waiting for handler shutdown")
+	}
+}
+
+// triggerEvent performs the actual event handling
+func (e *eventExecutor) triggerEvent(event Event, clientNameAssociation string, isDefault bool) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	// first run API handlers
+	for _, c := range e.apiRegistry[event.EventType] {
+		e.executeHandler(*c, event)
+	}
+
+	// then run Client handlers for name association
+
+	// first direct associates
+	associateClientRegistry := e.scopedRegistry[clientNameAssociation]
+	for _, c := range associateClientRegistry.callbacks[event.EventType] {
+		e.executeHandler(*c, event)
+	}
+
+	if !isDefault {
+		return
+	}
+
+	// handling the default provider - invoke default provider bound handlers by filtering
+
+	var defaultHandlers []EventCallback
+
+	for clientName, registry := range e.scopedRegistry {
+		if _, ok := e.namedProviderReference[clientName]; !ok {
+			defaultHandlers = append(defaultHandlers, registry.callbacks[event.EventType]...)
+		}
+	}
+
+	for _, c := range defaultHandlers {
+		e.executeHandler(*c, event)
+	}
+}
+
+// executeHandler is a helper which performs the actual invocation of the callback
+func (e *eventExecutor) executeHandler(f func(details EventDetails), event Event) {
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				e.logger.Info("recovered from a panic")
+			}
+		}()
+
+		f(EventDetails{
+			providerName: event.ProviderName,
+			ProviderEventDetails: ProviderEventDetails{
+				Message:       event.Message,
+				FlagChanges:   event.FlagChanges,
+				EventMetadata: event.EventMetadata,
+			},
+		})
+	}()
+}
diff --git a/pkg/openfeature/openfeature.go b/pkg/openfeature/openfeature.go
index 5d71f28..1b875f7 100644
--- a/pkg/openfeature/openfeature.go
+++ b/pkg/openfeature/openfeature.go
@@ -49,6 +49,40 @@ func AddHooks(hooks ...Hook) {
 	api.addHooks(hooks...)
 }
 
+// AddHandler allows to add API level event handler
+func AddHandler(eventType EventType, callback EventCallback) {
+	api.registerApiHandler(eventType, callback)
+}
+
+// addClientHandler is a helper for Client to add an event handler
+func addClientHandler(name string, t EventType, c EventCallback) {
+	api.registerClientHandler(name, t, c)
+}
+
+// RemoveHandler allows to remove API level event handler
+func RemoveHandler(eventType EventType, callback EventCallback) {
+	api.removeApiHandler(eventType, callback)
+}
+
+// removeClientHandler is a helper for Client to add an event handler
+func removeClientHandler(name string, t EventType, c EventCallback) {
+	api.removeClientHandler(name, t, c)
+}
+
+// getAPIEventRegistry is a helper for testing
+func getAPIEventRegistry() map[EventType][]EventCallback {
+	return api.eventExecutor.apiRegistry
+}
+
+// getClientRegistry is a helper for testing
+func getClientRegistry(client string) *scopedCallback {
+	if v, ok := api.eventExecutor.scopedRegistry[client]; ok {
+		return &v
+	}
+
+	return nil
+}
+
 // Shutdown active providers
 func Shutdown() {
 	api.shutdown()
diff --git a/pkg/openfeature/provider.go b/pkg/openfeature/provider.go
index bcf3af7..b51543a 100644
--- a/pkg/openfeature/provider.go
+++ b/pkg/openfeature/provider.go
@@ -27,6 +27,11 @@ const (
 	ReadyState    State = "READY"
 	ErrorState    State = "ERROR"
 
+	ProviderReady        EventType = "PROVIDER_READY"
+	ProviderConfigChange EventType = "PROVIDER_CONFIGURATION_CHANGED"
+	ProviderStale        EventType = "PROVIDER_STALE"
+	ProviderError        EventType = "PROVIDER_ERROR"
+
 	TargetingKey string = "targetingKey" // evaluation context map key. The targeting key uniquely identifies the subject (end-user, or client service) of a flag evaluation.
 )
 
@@ -60,6 +65,62 @@ type StateHandler interface {
 	Status() State
 }
 
+// NoopStateHandler is a noop StateHandler implementation
+// Status always set to ReadyState to comply with specification
+type NoopStateHandler struct {
+}
+
+func (s *NoopStateHandler) Init(e EvaluationContext) {
+	// NOOP
+}
+
+func (s *NoopStateHandler) Shutdown() {
+	// NOOP
+}
+
+func (s *NoopStateHandler) Status() State {
+	return ReadyState
+}
+
+// Eventing
+
+// EventHandler is the eventing contract enforced for FeatureProvider
+type EventHandler interface {
+	EventChannel() <-chan Event
+}
+
+// EventType emitted by a provider implementation
+type EventType string
+
+// ProviderEventDetails is the event payload emitted by FeatureProvider
+type ProviderEventDetails struct {
+	Message       string
+	FlagChanges   []string
+	EventMetadata map[string]interface{}
+}
+
+// Event is an event emitted by a FeatureProvider.
+type Event struct {
+	ProviderName string
+	EventType
+	ProviderEventDetails
+}
+
+type EventDetails struct {
+	providerName string
+	ProviderEventDetails
+}
+
+type EventCallback *func(details EventDetails)
+
+// SimpleEventHandler is the out-of-the-box EventHandler which is noop
+type SimpleEventHandler struct {
+}
+
+func (s SimpleEventHandler) EventChannel() <-chan Event {
+	return make(chan Event, 1)
+}
+
 // ProviderResolutionDetail is a structure which contains a subset of the fields defined in the EvaluationDetail,
 // representing the result of the provider's flag resolution process
 // see https://github.com/open-feature/spec/blob/main/specification/types.md#resolution-details


Test patch:
diff --git a/pkg/openfeature/event_executor_test.go b/pkg/openfeature/event_executor_test.go
new file mode 100644
index 0000000..245a8dc
--- /dev/null
+++ b/pkg/openfeature/event_executor_test.go
@@ -0,0 +1,562 @@
+package openfeature
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/go-logr/logr"
+	"github.com/open-feature/go-sdk/pkg/openfeature/internal"
+	"golang.org/x/exp/slices"
+)
+
+var logger logr.Logger
+
+func init() {
+	logger = logr.New(internal.Logger{})
+}
+
+// Requirement 5.1.1 The provider MAY define a mechanism for signaling the occurrence of one of a set of events,
+// including PROVIDER_READY, PROVIDER_ERROR, PROVIDER_CONFIGURATION_CHANGED and PROVIDER_STALE,
+// with a provider event details payload.
+func TestEventHandler_RegisterUnregisterEventProvider(t *testing.T) {
+
+	t.Run("Ignored non-eventing providers", func(t *testing.T) {
+		executor := newEventExecutor(logger)
+		err := executor.registerDefaultProvider(NoopProvider{})
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if executor.defaultProviderReference != nil {
+			t.Errorf("implementation should ignore non eventing provider")
+		}
+
+		err = executor.registerNamedEventingProvider("name", NoopProvider{})
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if len(executor.namedProviderReference) != 0 {
+			t.Fatalf("implementation should ignore non eventing provider")
+		}
+	})
+
+	t.Run("Accepts addition of eventing providers", func(t *testing.T) {
+		eventingImpl := &ProviderEventing{}
+
+		eventingProvider := struct {
+			FeatureProvider
+			EventHandler
+		}{
+			NoopProvider{},
+			eventingImpl,
+		}
+
+		executor := newEventExecutor(logger)
+		err := executor.registerDefaultProvider(eventingProvider)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if executor.defaultProviderReference == nil {
+			t.Errorf("implementation should register eventing provider")
+		}
+
+		err = executor.registerNamedEventingProvider("name", eventingProvider)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if _, ok := executor.namedProviderReference["name"]; !ok {
+			t.Errorf("implementation should register named eventing provider")
+		}
+	})
+}
+
+// Requirement 5.1.2 When a provider signals the occurrence of a particular event,
+// the associated client and API event handlers MUST run.
+func TestEventHandler_Eventing(t *testing.T) {
+	t.Run("Simple API level event", func(t *testing.T) {
+		eventingImpl := &ProviderEventing{
+			c: make(chan Event, 1),
+		}
+
+		eventingProvider := struct {
+			FeatureProvider
+			EventHandler
+		}{
+			NoopProvider{},
+			eventingImpl,
+		}
+
+		executor := newEventExecutor(logger)
+		err := executor.registerDefaultProvider(eventingProvider)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		rsp := make(chan EventDetails)
+		callBack := func(details EventDetails) {
+			rsp <- details
+		}
+
+		executor.registerApiHandler(ProviderReady, &callBack)
+
+		fCh := []string{"flagA"}
+		meta := map[string]interface{}{
+			"key": "value",
+		}
+
+		// trigger event from provider implementation
+		eventingImpl.Invoke(Event{
+			EventType: ProviderReady,
+			ProviderEventDetails: ProviderEventDetails{
+				Message:       "ReadyMessage",
+				FlagChanges:   fCh,
+				EventMetadata: meta,
+			},
+		})
+
+		// wait for execution
+		var result EventDetails
+		select {
+		case result = <-rsp:
+			break
+		case <-time.After(200 * time.Millisecond):
+			t.Fatalf("timeout - event did not trigger")
+		}
+		if result.Message != "ReadyMessage" {
+			t.Errorf("expected %s, but got %s", "EventMessage", result.Message)
+		}
+
+		if !slices.Equal(result.FlagChanges, fCh) {
+			t.Errorf("flag changes are not equal")
+		}
+
+		if !reflect.DeepEqual(result.EventMetadata, meta) {
+			t.Errorf("metadata are not equal")
+		}
+	})
+
+	t.Run("Simple Client level event", func(t *testing.T) {
+		eventingImpl := &ProviderEventing{
+			c: make(chan Event, 1),
+		}
+
+		// NoopProvider backed event supported provider
+		eventingProvider := struct {
+			FeatureProvider
+			EventHandler
+		}{
+			NoopProvider{},
+			eventingImpl,
+		}
+
+		executor := newEventExecutor(logger)
+
+		// associated to client name
+		associatedName := "providerForClient"
+
+		err := executor.registerNamedEventingProvider(associatedName, eventingProvider)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		rsp := make(chan EventDetails)
+		callBack := func(details EventDetails) {
+			rsp <- details
+		}
+
+		executor.registerClientHandler(associatedName, ProviderReady, &callBack)
+
+		fCh := []string{"flagA"}
+		meta := map[string]interface{}{
+			"key": "value",
+		}
+
+		// trigger event from provider implementation
+		eventingImpl.Invoke(Event{
+			ProviderName: eventingProvider.Metadata().Name,
+			EventType:    ProviderReady,
+			ProviderEventDetails: ProviderEventDetails{
+				Message:       "ReadyMessage",
+				FlagChanges:   fCh,
+				EventMetadata: meta,
+			},
+		})
+
+		// wait for execution
+		var result EventDetails
+		select {
+		case result = <-rsp:
+			break
+		case <-time.After(200 * time.Millisecond):
+			t.Fatalf("timeout - event did not trigger")
+		}
+
+		if result.providerName != eventingProvider.Metadata().Name {
+			t.Errorf("expected %s, but got %s", eventingProvider.Metadata().Name, result.providerName)
+		}
+
+		if result.Message != "ReadyMessage" {
+			t.Errorf("expected %s, but got %s", "EventMessage", result.Message)
+		}
+
+		if !slices.Equal(result.FlagChanges, fCh) {
+			t.Errorf("flag changes are not equal")
+		}
+
+		if !reflect.DeepEqual(result.EventMetadata, meta) {
+			t.Errorf("metadata are not equal")
+		}
+	})
+}
+
+// Requirement 5.1.3 When a provider signals the occurrence of a particular event,
+// event handlers on clients which are not associated with that provider MUST NOT run.
+func TestEventHandler_clientAssociation(t *testing.T) {
+	eventingImpl := &ProviderEventing{
+		c: make(chan Event, 1),
+	}
+
+	// eventing supported provider
+	defaultProvider := struct {
+		FeatureProvider
+		EventHandler
+	}{
+		NoopProvider{},
+		eventingImpl,
+	}
+
+	executor := newEventExecutor(logger)
+
+	// default provider
+	err := executor.registerDefaultProvider(defaultProvider)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// named provider(associated to name someClient)
+	err = executor.registerNamedEventingProvider("someClient", struct {
+		FeatureProvider
+		EventHandler
+	}{
+		NoopProvider{},
+		&ProviderEventing{
+			c: make(chan Event, 1),
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rsp := make(chan EventDetails)
+	callBack := func(details EventDetails) {
+		rsp <- details
+	}
+
+	event := ProviderReady
+	executor.registerClientHandler("someClient", event, &callBack)
+
+	// invoke default provider
+	eventingImpl.Invoke(Event{
+		ProviderName:         defaultProvider.Metadata().Name,
+		EventType:            event,
+		ProviderEventDetails: ProviderEventDetails{},
+	})
+
+	select {
+	case <-rsp:
+		t.Fatalf("incorrect association - executor must not have been invoked")
+	case <-time.After(200 * time.Millisecond):
+		break
+	}
+}
+
+// Requirement 5.2.5 If a handler function terminates abnormally, other handler functions MUST run.
+func TestEventHandler_ErrorHandling(t *testing.T) {
+	errorCallback := func(e EventDetails) {
+		panic("callback panic")
+	}
+
+	rsp := make(chan EventDetails, 1)
+	successAPICallback := func(e EventDetails) {
+		rsp <- e
+	}
+
+	rspClient := make(chan EventDetails, 1)
+	successClientCallback := func(e EventDetails) {
+		rspClient <- e
+	}
+
+	executor := newEventExecutor(logger)
+
+	// api level handlers
+	executor.registerApiHandler(ProviderReady, &errorCallback)
+	executor.registerApiHandler(ProviderReady, &successAPICallback)
+
+	// provider association
+	provider := "providerA"
+
+	// client level handlers
+	executor.registerClientHandler(provider, ProviderReady, &errorCallback)
+	executor.registerClientHandler(provider, ProviderReady, &successClientCallback)
+
+	// trigger events manually
+	go func() {
+		executor.triggerEvent(Event{
+			ProviderName:         provider,
+			EventType:            ProviderReady,
+			ProviderEventDetails: ProviderEventDetails{},
+		}, "", true)
+	}()
+
+	select {
+	case <-rsp:
+		break
+	case <-time.After(200 * time.Millisecond):
+		t.Error("API level callback timeout - executor recovery was not successful")
+	}
+
+	select {
+	case <-rspClient:
+		break
+	case <-time.After(200 * time.Millisecond):
+		t.Error("client callback timeout - executor recovery was not successful")
+	}
+}
+
+// Requirement 5.3.3 PROVIDER_READY handlers attached after the provider is already in a ready state MUST run immediately.
+func TestEventHandler_ProviderReadiness(t *testing.T) {
+	readyEventingProvider := struct {
+		FeatureProvider
+		StateHandler
+		EventHandler
+	}{
+		NoopProvider{},
+		&stateHandlerForTests{
+			State: ReadyState,
+		},
+		&ProviderEventing{},
+	}
+
+	executor := newEventExecutor(logger)
+
+	clientAssociation := "clientA"
+	err := executor.registerNamedEventingProvider(clientAssociation, readyEventingProvider)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rsp := make(chan EventDetails, 1)
+	successAPICallback := func(e EventDetails) {
+		rsp <- e
+	}
+
+	executor.registerClientHandler(clientAssociation, ProviderReady, &successAPICallback)
+
+	select {
+	case <-rsp:
+		break
+	case <-time.After(200 * time.Millisecond):
+		t.Errorf("timedout waiting for ready state callback, but got none")
+	}
+}
+
+// Contract tests - registration & removal
+
+func TestEventHandler_Registration(t *testing.T) {
+	t.Run("API handlers", func(t *testing.T) {
+		executor := newEventExecutor(logger)
+
+		// Add multiple - ProviderReady
+		executor.registerApiHandler(ProviderReady, &h1)
+		executor.registerApiHandler(ProviderReady, &h2)
+		executor.registerApiHandler(ProviderReady, &h3)
+		executor.registerApiHandler(ProviderReady, &h4)
+
+		// Add multiple - ProviderError
+		executor.registerApiHandler(ProviderError, &h2)
+		executor.registerApiHandler(ProviderError, &h2)
+
+		// Add single types
+		executor.registerApiHandler(ProviderStale, &h3)
+		executor.registerApiHandler(ProviderConfigChange, &h4)
+
+		readyLen := len(executor.apiRegistry[ProviderReady])
+		if readyLen != 4 {
+			t.Errorf("expected %d events, but got %d", 4, readyLen)
+		}
+
+		errLen := len(executor.apiRegistry[ProviderError])
+		if errLen != 2 {
+			t.Errorf("expected %d events, but got %d", 2, errLen)
+		}
+
+		staleLen := len(executor.apiRegistry[ProviderStale])
+		if staleLen != 1 {
+			t.Errorf("expected %d events, but got %d", 1, staleLen)
+		}
+
+		cfgLen := len(executor.apiRegistry[ProviderConfigChange])
+		if cfgLen != 1 {
+			t.Errorf("expected %d events, but got %d", 1, cfgLen)
+		}
+	})
+
+	t.Run("Client handlers", func(t *testing.T) {
+		executor := newEventExecutor(logger)
+
+		// Add multiple - client a
+		executor.registerClientHandler("a", ProviderReady, &h1)
+		executor.registerClientHandler("a", ProviderReady, &h2)
+		executor.registerClientHandler("a", ProviderReady, &h3)
+		executor.registerClientHandler("a", ProviderReady, &h4)
+
+		// Add single for rest of the client
+		executor.registerClientHandler("b", ProviderError, &h2)
+		executor.registerClientHandler("c", ProviderStale, &h3)
+		executor.registerClientHandler("d", ProviderConfigChange, &h4)
+
+		readyLen := len(executor.scopedRegistry["a"].callbacks[ProviderReady])
+		if readyLen != 4 {
+			t.Errorf("expected %d events in client a, but got %d", 4, readyLen)
+		}
+
+		errLen := len(executor.scopedRegistry["b"].callbacks[ProviderError])
+		if errLen != 1 {
+			t.Errorf("expected %d events in client b, but got %d", 1, errLen)
+		}
+
+		staleLen := len(executor.scopedRegistry["c"].callbacks[ProviderStale])
+		if staleLen != 1 {
+			t.Errorf("expected %d events in client c, but got %d", 1, staleLen)
+		}
+
+		cfgLen := len(executor.scopedRegistry["d"].callbacks[ProviderConfigChange])
+		if cfgLen != 1 {
+			t.Errorf("expected %d events in client d, but got %d", 1, cfgLen)
+		}
+	})
+}
+
+func TestEventHandler_APIRemoval(t *testing.T) {
+	t.Run("API level removal", func(t *testing.T) {
+		executor := newEventExecutor(logger)
+
+		// Add multiple - ProviderReady
+		executor.registerApiHandler(ProviderReady, &h1)
+		executor.registerApiHandler(ProviderReady, &h2)
+		executor.registerApiHandler(ProviderReady, &h3)
+		executor.registerApiHandler(ProviderReady, &h4)
+
+		// Add single types
+		executor.registerApiHandler(ProviderError, &h2)
+		executor.registerApiHandler(ProviderStale, &h3)
+		executor.registerApiHandler(ProviderConfigChange, &h4)
+
+		// removal
+		executor.removeApiHandler(ProviderReady, &h1)
+		executor.removeApiHandler(ProviderError, &h2)
+		executor.removeApiHandler(ProviderStale, &h3)
+		executor.removeApiHandler(ProviderConfigChange, &h4)
+
+		readyLen := len(executor.apiRegistry[ProviderReady])
+		if readyLen != 3 {
+			t.Errorf("expected %d events, but got %d", 3, readyLen)
+		}
+
+		if !slices.Contains(executor.apiRegistry[ProviderReady], &h2) {
+			t.Errorf("expected callback to be present")
+		}
+
+		if !slices.Contains(executor.apiRegistry[ProviderReady], &h3) {
+			t.Errorf("expected callback to be present")
+		}
+
+		if !slices.Contains(executor.apiRegistry[ProviderReady], &h3) {
+			t.Errorf("expected callback to be present")
+		}
+
+		errLen := len(executor.apiRegistry[ProviderError])
+		if errLen != 0 {
+			t.Errorf("expected %d events, but got %d", 0, errLen)
+		}
+
+		staleLen := len(executor.apiRegistry[ProviderStale])
+		if staleLen != 0 {
+			t.Errorf("expected %d events, but got %d", 0, staleLen)
+		}
+
+		cfgLen := len(executor.apiRegistry[ProviderConfigChange])
+		if cfgLen != 0 {
+			t.Errorf("expected %d events, but got %d", 0, cfgLen)
+		}
+	})
+
+	t.Run("Client level removal", func(t *testing.T) {
+		executor := newEventExecutor(logger)
+
+		// Add multiple - client a
+		executor.registerClientHandler("a", ProviderReady, &h1)
+		executor.registerClientHandler("a", ProviderReady, &h2)
+		executor.registerClientHandler("a", ProviderReady, &h3)
+		executor.registerClientHandler("a", ProviderReady, &h4)
+
+		// Add single
+		executor.registerClientHandler("b", ProviderError, &h2)
+		executor.registerClientHandler("c", ProviderStale, &h3)
+		executor.registerClientHandler("d", ProviderConfigChange, &h4)
+
+		// removal
+		executor.removeClientHandler("a", ProviderReady, &h1)
+		executor.removeClientHandler("b", ProviderError, &h2)
+		executor.removeClientHandler("c", ProviderStale, &h3)
+		executor.removeClientHandler("d", ProviderConfigChange, &h4)
+
+		readyLen := len(executor.scopedRegistry["a"].callbacks[ProviderReady])
+		if readyLen != 3 {
+			t.Errorf("expected %d events in client a, but got %d", 3, readyLen)
+		}
+
+		if !slices.Contains(executor.scopedRegistry["a"].callbacks[ProviderReady], &h2) {
+			t.Errorf("expected callback to be present")
+		}
+
+		if !slices.Contains(executor.scopedRegistry["a"].callbacks[ProviderReady], &h3) {
+			t.Errorf("expected callback to be present")
+		}
+
+		if !slices.Contains(executor.scopedRegistry["a"].callbacks[ProviderReady], &h4) {
+			t.Errorf("expected callback to be present")
+		}
+
+		errLen := len(executor.scopedRegistry["b"].callbacks[ProviderError])
+		if errLen != 0 {
+			t.Errorf("expected %d events in client b, but got %d", 0, errLen)
+		}
+
+		staleLen := len(executor.scopedRegistry["c"].callbacks[ProviderStale])
+		if staleLen != 0 {
+			t.Errorf("expected %d events in client c, but got %d", 0, staleLen)
+		}
+
+		cfgLen := len(executor.scopedRegistry["d"].callbacks[ProviderConfigChange])
+		if cfgLen != 0 {
+			t.Errorf("expected %d events in client d, but got %d", 0, cfgLen)
+		}
+
+		// removal referenced to non-existing clients does nothing & no panics
+		executor.removeClientHandler("non-existing", ProviderReady, &h1)
+		executor.removeClientHandler("b", ProviderReady, &h1)
+	})
+
+	t.Run("remove handlers that were not added", func(t *testing.T) {
+		executor := newEventExecutor(logger)
+
+		// removal of non-added handlers shall not panic
+		executor.removeApiHandler(ProviderReady, &h1)
+		executor.removeClientHandler("a", ProviderReady, &h1)
+	})
+}
diff --git a/pkg/openfeature/openfeature_test.go b/pkg/openfeature/openfeature_test.go
index 98da6bb..f2f7337 100644
--- a/pkg/openfeature/openfeature_test.go
+++ b/pkg/openfeature/openfeature_test.go
@@ -2,11 +2,12 @@ package openfeature
 
 import (
 	"github.com/go-logr/logr"
-	"github.com/golang/mock/gomock"
 	"github.com/open-feature/go-sdk/pkg/openfeature/internal"
 	"reflect"
 	"testing"
 	"time"
+
+	"github.com/golang/mock/gomock"
 )
 
 // The `API`, and any state it maintains SHOULD exist as a global singleton,
@@ -158,7 +159,7 @@ func TestRequirement_1_1_2_3(t *testing.T) {
 		case <-initSem:
 			break
 		}
-		
+
 		providerOverride, _, _ := setupProviderWithSemaphores()
 
 		err = SetNamedProvider(client, providerOverride)
@@ -329,6 +330,123 @@ func TestRequirement_1_6_1(t *testing.T) {
 	}
 }
 
+func TestRequirement_EventCompliance(t *testing.T) {
+
+	// The client MUST provide a function for associating handler functions with a particular provider event type.
+	// The API and client MUST provide a function allowing the removal of event handlers.
+	t.Run("requirement_5_2_1 & requirement_5_2_7", func(t *testing.T) {
+		defer t.Cleanup(initSingleton)
+
+		clientName := "OFClient"
+
+		client := NewClient(clientName)
+
+		// adding handlers
+		client.AddHandler(ProviderReady, &h1)
+		client.AddHandler(ProviderError, &h1)
+		client.AddHandler(ProviderStale, &h1)
+		client.AddHandler(ProviderConfigChange, &h1)
+
+		registry := getClientRegistry(clientName)
+
+		if registry == nil {
+			t.Fatalf("no event handler registry present for client name %s", clientName)
+		}
+
+		if len(registry.callbacks[ProviderReady]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		if len(registry.callbacks[ProviderError]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		if len(registry.callbacks[ProviderStale]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		if len(registry.callbacks[ProviderConfigChange]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		// removing handlers
+		client.RemoveHandler(ProviderReady, &h1)
+		client.RemoveHandler(ProviderError, &h1)
+		client.RemoveHandler(ProviderStale, &h1)
+		client.RemoveHandler(ProviderConfigChange, &h1)
+
+		if len(registry.callbacks[ProviderReady]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+
+		if len(registry.callbacks[ProviderError]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+
+		if len(registry.callbacks[ProviderStale]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+
+		if len(registry.callbacks[ProviderConfigChange]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+	})
+
+	// The API MUST provide a function for associating handler functions with a particular provider event type.
+	// The API and client MUST provide a function allowing the removal of event handlers.
+	t.Run("requirement_5_2_2 & requirement_5_2_7", func(t *testing.T) {
+		defer t.Cleanup(initSingleton)
+
+		// adding handlers
+		AddHandler(ProviderReady, &h1)
+		AddHandler(ProviderError, &h1)
+		AddHandler(ProviderStale, &h1)
+		AddHandler(ProviderConfigChange, &h1)
+
+		registry := getAPIEventRegistry()
+
+		if len(registry[ProviderReady]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		if len(registry[ProviderError]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		if len(registry[ProviderStale]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		if len(registry[ProviderConfigChange]) < 1 {
+			t.Errorf("expected a registry regiration, but got none")
+		}
+
+		// removing handlers
+		RemoveHandler(ProviderReady, &h1)
+		RemoveHandler(ProviderError, &h1)
+		RemoveHandler(ProviderStale, &h1)
+		RemoveHandler(ProviderConfigChange, &h1)
+
+		registry = getAPIEventRegistry()
+
+		if len(registry[ProviderReady]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+
+		if len(registry[ProviderError]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+
+		if len(registry[ProviderStale]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+
+		if len(registry[ProviderConfigChange]) > 0 {
+			t.Errorf("expected empty registrations")
+		}
+	})
+}
+
 // Non-spec bound validations
 
 // If there is no client name bound provider, then return the default provider
diff --git a/pkg/openfeature/provider_mock_test.go b/pkg/openfeature/provider_mock_test.go
index d202147..3fb4c3d 100644
--- a/pkg/openfeature/provider_mock_test.go
+++ b/pkg/openfeature/provider_mock_test.go
@@ -193,39 +193,39 @@ func (mr *MockStateHandlerMockRecorder) Status() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockStateHandler)(nil).Status))
 }
 
-// MockState is a mock of State interface.
-type MockState struct {
+// MockEventHandler is a mock of EventHandler interface.
+type MockEventHandler struct {
 	ctrl     *gomock.Controller
-	recorder *MockStateMockRecorder
+	recorder *MockEventHandlerMockRecorder
 }
 
-// MockStateMockRecorder is the mock recorder for MockState.
-type MockStateMockRecorder struct {
-	mock *MockState
+// MockEventHandlerMockRecorder is the mock recorder for MockEventHandler.
+type MockEventHandlerMockRecorder struct {
+	mock *MockEventHandler
 }
 
-// NewMockState creates a new mock instance.
-func NewMockState(ctrl *gomock.Controller) *MockState {
-	mock := &MockState{ctrl: ctrl}
-	mock.recorder = &MockStateMockRecorder{mock}
+// NewMockEventHandler creates a new mock instance.
+func NewMockEventHandler(ctrl *gomock.Controller) *MockEventHandler {
+	mock := &MockEventHandler{ctrl: ctrl}
+	mock.recorder = &MockEventHandlerMockRecorder{mock}
 	return mock
 }
 
 // EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockState) EXPECT() *MockStateMockRecorder {
+func (m *MockEventHandler) EXPECT() *MockEventHandlerMockRecorder {
 	return m.recorder
 }
 
-// get mocks base method.
-func (m *MockState) get() string {
+// EventChannel mocks base method.
+func (m *MockEventHandler) EventChannel() <-chan Event {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "get")
-	ret0, _ := ret[0].(string)
+	ret := m.ctrl.Call(m, "EventChannel")
+	ret0, _ := ret[0].(<-chan Event)
 	return ret0
 }
 
-// get indicates an expected call of get.
-func (mr *MockStateMockRecorder) get() *gomock.Call {
+// EventChannel indicates an expected call of EventChannel.
+func (mr *MockEventHandlerMockRecorder) EventChannel() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "get", reflect.TypeOf((*MockState)(nil).get))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventChannel", reflect.TypeOf((*MockEventHandler)(nil).EventChannel))
 }
diff --git a/pkg/openfeature/util_test.go b/pkg/openfeature/util_test.go
index 0f5522e..08f4866 100644
--- a/pkg/openfeature/util_test.go
+++ b/pkg/openfeature/util_test.go
@@ -2,6 +2,31 @@ package openfeature
 
 // Test Utils
 
+// event handlers
+var h1 func(details EventDetails)
+var h2 func(details EventDetails)
+var h3 func(details EventDetails)
+var h4 func(details EventDetails)
+
+func init() {
+	h1 = func(details EventDetails) {
+		// noop
+	}
+
+	h2 = func(details EventDetails) {
+		// noop
+	}
+
+	h3 = func(details EventDetails) {
+		// noop
+	}
+
+	h4 = func(details EventDetails) {
+		// noop
+	}
+}
+
+// stateHandlerForTests is a StateHandler with callbacks
 type stateHandlerForTests struct {
 	initF     func(e EvaluationContext)
 	shutdownF func()
@@ -19,3 +44,16 @@ func (s *stateHandlerForTests) Shutdown() {
 func (s *stateHandlerForTests) Status() State {
 	return s.State
 }
+
+// ProviderEventing is an eventing implementation with invoke capability
+type ProviderEventing struct {
+	c chan Event
+}
+
+func (s ProviderEventing) Invoke(e Event) {
+	s.c <- e
+}
+
+func (s ProviderEventing) EventChannel() <-chan Event {
+	return s.c
+}