Repository: open-feature/js-sdk
Pull request #795 (2024-01-29 20:45:15)

feat: maintain state in SDK, add RECONCILING
Implements the newest spec changes [here](https://github.com/open-feature/spec/pull/241).

- Provider state is deprecated (state is now maintained in the SDK itself). This is non-breaking, we just ignore the provider's own state now
- add RECONCILING events and state, fire related events with provider's `on context change`
- add FATAL state

The new tests should help you understand the impact of the changes.

github-actions[bot]: Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.

Details:

```
No release type found in pull request title "[PoC] feat!: deprecate status, run stale automatically". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit
```
<!-- Sticky Pull Request Commentpr-title-lint-error -->
toddbaert: @lukas-reining thanks for the great review. I've resolved some of your smaller comments, and left a few others open for your re-review.
toddbaert: > he provider section in the client and server readmes.

Good point. I removed the provider status there.

Patch:
diff --git a/jest.config.ts b/jest.config.ts
index d7b24972..55818da3 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -174,7 +174,7 @@ export default {
   ],
 
   // Use this configuration option to add custom reporters to Jest
-  reporters: [['github-actions', { silent: false }], 'summary'],
+  reporters: ['default', ['github-actions', { silent: false }], 'summary'],
 
   // Automatically reset mock state before every test
   // resetMocks: false,
diff --git a/package.json b/package.json
index 738d12c0..97e13c2f 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "private": true,
   "description": "OpenFeature SDK for JavaScript",
   "scripts": {
-    "test": "jest --selectProjects=shared --selectProjects=server --selectProjects=client --verbose",
+    "test": "jest --selectProjects=shared --selectProjects=server --selectProjects=client --silent",
     "e2e-server": "git submodule update --init --recursive && shx cp test-harness/features/evaluation.feature packages/server/e2e/features && jest --selectProjects=server-e2e --verbose",
     "e2e-client": "git submodule update --init --recursive && shx cp test-harness/features/evaluation.feature packages/client/e2e/features && jest --selectProjects=client-e2e --verbose",
     "e2e": "npm run e2e-server && npm run e2e-client",
diff --git a/packages/client/README.md b/packages/client/README.md
index f19fdf31..87e0c7ce 100644
--- a/packages/client/README.md
+++ b/packages/client/README.md
@@ -279,7 +279,6 @@ import {
   Logger,
   Provider,
   ProviderEventEmitter,
-  ProviderStatus,
   ResolutionDetails
 } from '@openfeature/web-sdk';
 
@@ -309,8 +308,6 @@ class MyProvider implements Provider {
     // reconcile the provider's cached flags, if applicable
   }
 
-  status?: ProviderStatus | undefined;
-
   // implement with "new OpenFeatureEventEmitter()", and use "emit()" to emit events
   events?: ProviderEventEmitter<AnyProviderEvent> | undefined; 
 
diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts
index 2eec5c8e..fd8efe7e 100644
--- a/packages/client/src/client/client.ts
+++ b/packages/client/src/client/client.ts
@@ -1,5 +1,6 @@
-import { ClientMetadata, EvaluationLifeCycle, Eventing, ManageLogger, ProviderStatus } from '@openfeature/core';
+import { ClientMetadata, EvaluationLifeCycle, Eventing, ManageLogger } from '@openfeature/core';
 import { Features } from '../evaluation';
+import { ProviderStatus } from '../provider';
 
 export interface Client extends EvaluationLifeCycle<Client>, Features, ManageLogger<Client>, Eventing {
   readonly metadata: ClientMetadata;
@@ -7,4 +8,4 @@ export interface Client extends EvaluationLifeCycle<Client>, Features, ManageLog
    * Returns the status of the associated provider.
    */
   readonly providerStatus: ProviderStatus;
-}
\ No newline at end of file
+}
diff --git a/packages/client/src/client/open-feature-client.ts b/packages/client/src/client/open-feature-client.ts
index 6f14dc24..3fca4d6b 100644
--- a/packages/client/src/client/open-feature-client.ts
+++ b/packages/client/src/client/open-feature-client.ts
@@ -10,7 +10,7 @@ import {
   JsonValue,
   Logger,
   OpenFeatureError,
-  ProviderStatus,
+  ProviderFatalError,
   ResolutionDetails,
   SafeLogger,
   StandardResolutionReasons,
@@ -21,8 +21,9 @@ import { ProviderEvents } from '../events';
 import { InternalEventEmitter } from '../events/internal/internal-event-emitter';
 import { Hook } from '../hooks';
 import { OpenFeature } from '../open-feature';
-import { Provider } from '../provider';
+import { Provider, ProviderStatus } from '../provider';
 import { Client } from './client';
+import { ProviderNotReadyError } from '@openfeature/core';
 
 type OpenFeatureClientOptions = {
   /**
@@ -41,6 +42,7 @@ export class OpenFeatureClient implements Client {
     // functions are passed here to make sure that these values are always up to date,
     // and so we don't have to make these public properties on the API class.
     private readonly providerAccessor: () => Provider,
+    private readonly providerStatusAccessor: () => ProviderStatus,
     private readonly emitterAccessor: () => InternalEventEmitter,
     private readonly globalLogger: () => Logger,
     private readonly options: OpenFeatureClientOptions,
@@ -57,12 +59,12 @@ export class OpenFeatureClient implements Client {
   }
 
   get providerStatus(): ProviderStatus {
-    return this.providerAccessor()?.status || ProviderStatus.READY;
+    return this.providerStatusAccessor();
   }
 
   addHandler(eventType: ProviderEvents, handler: EventHandler): void {
     this.emitterAccessor().addHandler(eventType, handler);
-    const shouldRunNow = statusMatchesEvent(eventType, this._provider.status);
+    const shouldRunNow = statusMatchesEvent(eventType, this.providerStatus);
 
     if (shouldRunNow) {
       // run immediately, we're in the matching state
@@ -206,6 +208,13 @@ export class OpenFeatureClient implements Client {
 
     try {
       this.beforeHooks(allHooks, hookContext, options);
+      
+      // short circuit evaluation entirely if provider is in a bad state
+      if (this.providerStatus === ProviderStatus.NOT_READY) {
+        throw new ProviderNotReadyError('provider has not yet initialized');
+      } else if (this.providerStatus === ProviderStatus.FATAL) {
+        throw new ProviderFatalError('provider is in an irrecoverable error state');
+      }
 
       // run the referenced resolver, binding the provider.
       const resolution = resolver.call(this._provider, flagKey, defaultValue, context, this._logger);
diff --git a/packages/client/src/open-feature.ts b/packages/client/src/open-feature.ts
index b3a3bb89..08fc424a 100644
--- a/packages/client/src/open-feature.ts
+++ b/packages/client/src/open-feature.ts
@@ -1,15 +1,17 @@
 import {
+  ClientProviderStatus,
   EvaluationContext,
   GenericEventEmitter,
   ManageContext,
   OpenFeatureCommonAPI,
+  ProviderWrapper,
   objectOrUndefined,
   stringOrUndefined,
 } from '@openfeature/core';
 import { Client, OpenFeatureClient } from './client';
 import { OpenFeatureEventEmitter, ProviderEvents } from './events';
 import { Hook } from './hooks';
-import { NOOP_PROVIDER, Provider } from './provider';
+import { NOOP_PROVIDER, Provider, ProviderStatus } from './provider';
 
 // use a symbol as a key for the global singleton
 const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/web-sdk/api');
@@ -19,14 +21,16 @@ type OpenFeatureGlobal = {
 };
 type DomainRecord = {
   domain?: string;
-  provider: Provider;
+  wrapper: ProviderWrapper<Provider, ClientProviderStatus>;
 };
 
 const _globalThis = globalThis as OpenFeatureGlobal;
 
-export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> implements ManageContext<Promise<void>> {
-  protected _events: GenericEventEmitter<ProviderEvents> = new OpenFeatureEventEmitter();
-  protected _defaultProvider: Provider = NOOP_PROVIDER;
+export class OpenFeatureAPI extends OpenFeatureCommonAPI<ClientProviderStatus, Provider, Hook> implements ManageContext<Promise<void>> {
+  protected _statusEnumType: typeof ProviderStatus = ProviderStatus;
+  protected _apiEmitter: GenericEventEmitter<ProviderEvents> = new OpenFeatureEventEmitter();
+  protected _defaultProvider: ProviderWrapper<Provider, ClientProviderStatus> = new ProviderWrapper(NOOP_PROVIDER, ProviderStatus.NOT_READY, this._statusEnumType);
+  protected _domainScopedProviders: Map<string, ProviderWrapper<Provider, ClientProviderStatus>> = new Map();
   protected _createEventEmitter = () => new OpenFeatureEventEmitter();
 
   private constructor() {
@@ -49,6 +53,14 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
     return instance;
   }
 
+  private getProviderStatus(domain?: string): ProviderStatus {
+    if (!domain) {
+      return this._defaultProvider.status;
+    }
+
+    return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
+  }
+
   /**
    * Sets the evaluation context globally.
    * This will be used by all providers that have not bound to a domain.
@@ -73,11 +85,11 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
     const context = objectOrUndefined<T>(domainOrContext) ?? objectOrUndefined(contextOrUndefined) ?? {};
 
     if (domain) {
-      const provider = this._domainScopedProviders.get(domain);
-      if (provider) {
+      const wrapper = this._domainScopedProviders.get(domain);
+      if (wrapper) {
         const oldContext = this.getContext(domain);
         this._domainScopedContext.set(domain, context);
-        await this.runProviderContextChangeHandler(domain, provider, oldContext, context);
+        await this.runProviderContextChangeHandler(domain, wrapper, oldContext, context);
       } else {
         this._domainScopedContext.set(domain, context);
       }
@@ -88,19 +100,19 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
       // collect all providers that are using the default context (not bound to a domain)
       const unboundProviders: DomainRecord[] = Array.from(this._domainScopedProviders.entries())
         .filter(([domain]) => !this._domainScopedContext.has(domain))
-        .reduce<DomainRecord[]>((acc, [domain, provider]) => {
-          acc.push({ domain, provider });
+        .reduce<DomainRecord[]>((acc, [domain, wrapper]) => {
+          acc.push({ domain, wrapper });
           return acc;
         }, []);
 
-      const allProviders: DomainRecord[] = [
+      const allDomainRecords: DomainRecord[] = [
         // add in the default (no domain)
-        { domain: undefined, provider: this._defaultProvider },
+        { domain: undefined, wrapper: this._defaultProvider },
         ...unboundProviders,
       ];
       await Promise.all(
-        allProviders.map((tuple) =>
-          this.runProviderContextChangeHandler(tuple.domain, tuple.provider, oldContext, context),
+        allDomainRecords.map((dm) =>
+          this.runProviderContextChangeHandler(dm.domain, dm.wrapper, oldContext, context),
         ),
       );
     }
@@ -143,12 +155,12 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
   async clearContext(domainOrUndefined?: string): Promise<void> {
     const domain = stringOrUndefined(domainOrUndefined);
     if (domain) {
-      const provider = this._domainScopedProviders.get(domain);
-      if (provider) {
+      const wrapper = this._domainScopedProviders.get(domain);
+      if (wrapper) {
         const oldContext = this.getContext(domain);
         this._domainScopedContext.delete(domain);
         const newContext = this.getContext();
-        await this.runProviderContextChangeHandler(domain, provider, oldContext, newContext);
+        await this.runProviderContextChangeHandler(domain, wrapper, oldContext, newContext);
       } else {
         this._domainScopedContext.delete(domain);
       }
@@ -186,6 +198,7 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
       // functions are passed here to make sure that these values are always up to date,
       // and so we don't have to make these public properties on the API class.
       () => this.getProviderForClient(domain),
+      () => this.getProviderStatus(domain),
       () => this.buildAndCacheEventEmitterForClient(domain),
       () => this._logger,
       { domain, version },
@@ -203,28 +216,45 @@ export class OpenFeatureAPI extends OpenFeatureCommonAPI<Provider, Hook> impleme
 
   private async runProviderContextChangeHandler(
     domain: string | undefined,
-    provider: Provider,
+    wrapper: ProviderWrapper<Provider, ClientProviderStatus>,
     oldContext: EvaluationContext,
     newContext: EvaluationContext,
   ): Promise<void> {
-    const providerName = provider.metadata.name;
+    // this should always be set according to the typings, but let's be defensive considering JS
+    const providerName = wrapper.provider?.metadata?.name || 'unnamed-provider';
+    
     try {
-      await provider.onContextChange?.(oldContext, newContext);
-
-      // only run the event handlers if the onContextChange method succeeded
-      this.getAssociatedEventEmitters(domain).forEach((emitter) => {
-        emitter?.emit(ProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
-      });
-      this._events?.emit(ProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
+      if (typeof wrapper.provider.onContextChange === 'function') {
+        wrapper.incrementPendingContextChanges();
+        wrapper.status = this._statusEnumType.RECONCILING;
+        this.getAssociatedEventEmitters(domain).forEach((emitter) => {
+          emitter?.emit(ProviderEvents.Reconciling, { domain, providerName });
+        });
+        this._apiEmitter?.emit(ProviderEvents.Reconciling, { domain, providerName });
+        await wrapper.provider.onContextChange(oldContext, newContext);
+        wrapper.decrementPendingContextChanges();
+      }
+      // only run the event handlers, and update the state if the onContextChange method succeeded
+      wrapper.status = this._statusEnumType.READY;
+      if (wrapper.allContextChangesSettled) {
+        this.getAssociatedEventEmitters(domain).forEach((emitter) => {
+          emitter?.emit(ProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
+        });
+        this._apiEmitter?.emit(ProviderEvents.ContextChanged, { clientName: domain, domain, providerName });
+      }
     } catch (err) {
       // run error handlers instead
-      const error = err as Error | undefined;
-      const message = `Error running ${provider?.metadata?.name}'s context change handler: ${error?.message}`;
-      this._logger?.error(`${message}`, err);
-      this.getAssociatedEventEmitters(domain).forEach((emitter) => {
-        emitter?.emit(ProviderEvents.Error, { clientName: domain, domain, providerName, message });
-      });
-      this._events?.emit(ProviderEvents.Error, { clientName: domain, domain, providerName, message });
+      wrapper.decrementPendingContextChanges();
+      wrapper.status = this._statusEnumType.ERROR;
+      if (wrapper.allContextChangesSettled) {
+        const error = err as Error | undefined;
+        const message = `Error running ${providerName}'s context change handler: ${error?.message}`;
+        this._logger?.error(`${message}`, err);
+        this.getAssociatedEventEmitters(domain).forEach((emitter) => {
+          emitter?.emit(ProviderEvents.Error, { clientName: domain, domain, providerName, message });
+        });
+        this._apiEmitter?.emit(ProviderEvents.Error, { clientName: domain, domain, providerName, message });
+      }
     }
   }
 }
diff --git a/packages/client/src/provider/in-memory-provider/in-memory-provider.ts b/packages/client/src/provider/in-memory-provider/in-memory-provider.ts
index 9261830b..84e7a36e 100644
--- a/packages/client/src/provider/in-memory-provider/in-memory-provider.ts
+++ b/packages/client/src/provider/in-memory-provider/in-memory-provider.ts
@@ -9,7 +9,6 @@ import {
   ResolutionDetails,
   StandardResolutionReasons,
   TypeMismatchError,
-  ProviderStatus,
 } from '@openfeature/core';
 import { Provider } from '../provider';
 import { OpenFeatureEventEmitter, ProviderEvents } from '../../events';
@@ -22,7 +21,6 @@ import { VariantNotFoundError } from './variant-not-found-error';
 export class InMemoryProvider implements Provider {
   public readonly events = new OpenFeatureEventEmitter();
   public readonly runsOn = 'client';
-  status: ProviderStatus = ProviderStatus.NOT_READY;
   readonly metadata = {
     name: 'in-memory',
   } as const;
@@ -35,18 +33,12 @@ export class InMemoryProvider implements Provider {
 
   async initialize(context?: EvaluationContext | undefined): Promise<void> {
     try {
-
       for (const key in this._flagConfiguration) {
         this.resolveFlagWithReason(key, context);
       }
-
       this._context = context;
-      // set the provider's state, but don't emit events manually;
-      // the SDK does this based on the resolution/rejection of the init promise
-      this.status = ProviderStatus.READY;
-    } catch (error) {
-      this.status = ProviderStatus.ERROR;
-      throw error;
+    } catch (err) {
+      throw new Error('initialization failure', { cause: err });
     }
   }
 
@@ -59,16 +51,10 @@ export class InMemoryProvider implements Provider {
       .filter(([key, value]) => this._flagConfiguration[key] !== value)
       .map(([key]) => key);
 
-    this.status = ProviderStatus.STALE;
-    this.events.emit(ProviderEvents.Stale);
-
     this._flagConfiguration = { ...flagConfiguration };
-    this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged });
-
     try {
       await this.initialize(this._context);
-      // we need to emit our own events in this case, since it's not part of the init flow.
-      this.events.emit(ProviderEvents.Ready);
+      this.events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged });
     } catch (err) {
       this.events.emit(ProviderEvents.Error);
       throw err;
@@ -165,9 +151,7 @@ export class InMemoryProvider implements Provider {
 
     const value = variant && flagSpec?.variants[variant];
 
-    const evalReason = isContextEval ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.STATIC;
-
-    const reason = this.status === ProviderStatus.STALE ? StandardResolutionReasons.CACHED : evalReason;
+    const reason = isContextEval ? StandardResolutionReasons.TARGETING_MATCH : StandardResolutionReasons.STATIC;
 
     return {
       value: value as T,
diff --git a/packages/client/src/provider/no-op-provider.ts b/packages/client/src/provider/no-op-provider.ts
index 8493bf3b..123bcb36 100644
--- a/packages/client/src/provider/no-op-provider.ts
+++ b/packages/client/src/provider/no-op-provider.ts
@@ -1,4 +1,4 @@
-import { JsonValue, ProviderStatus, ResolutionDetails } from '@openfeature/core';
+import { JsonValue, ResolutionDetails } from '@openfeature/core';
 import { Provider } from './provider';
 
 const REASON_NO_OP = 'No-op';
@@ -11,16 +11,6 @@ class NoopFeatureProvider implements Provider {
     name: 'No-op Provider',
   } as const;
 
-  get status(): ProviderStatus {
-    /**
-     * This is due to the NoopProvider not being a real provider.
-     * We do not want it to trigger the Ready event handlers, so we never set this to ready.
-     * With the NoopProvider assigned, the client can be assumed to be uninitialized.
-     * https://github.com/open-feature/js-sdk/pull/429#discussion_r1202642654
-     */
-    return ProviderStatus.NOT_READY;
-  }
-
   resolveBooleanEvaluation(_: string, defaultValue: boolean): ResolutionDetails<boolean> {
     return this.noOp(defaultValue);
   }
diff --git a/packages/client/src/provider/provider.ts b/packages/client/src/provider/provider.ts
index f15b01e6..8b590366 100644
--- a/packages/client/src/provider/provider.ts
+++ b/packages/client/src/provider/provider.ts
@@ -1,13 +1,15 @@
-import { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/core';
+import { ClientProviderStatus, CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/core';
 import { Hook } from '../hooks';
 
+export { ClientProviderStatus as ProviderStatus };
+
 /**
  * Interface that providers must implement to resolve flag values for their particular
  * backend or vendor.
  *
  * Implementation for resolving all the required flag types must be defined.
  */
-export interface Provider extends CommonProvider {
+export interface Provider extends CommonProvider<ClientProviderStatus> {
   /**
    * A provider hook exposes a mechanism for provider authors to register hooks
    * to tap into various stages of the flag evaluation lifecycle. These hooks can
diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json
index 6337d193..b1dbc8b3 100644
--- a/packages/client/tsconfig.json
+++ b/packages/client/tsconfig.json
@@ -59,7 +59,7 @@
     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
-    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+    "stripInternal": true,                               /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
     // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
diff --git a/packages/nest/src/feature.decorator.ts b/packages/nest/src/feature.decorator.ts
index 6f61daf5..4d8cb96a 100644
--- a/packages/nest/src/feature.decorator.ts
+++ b/packages/nest/src/feature.decorator.ts
@@ -56,7 +56,7 @@ interface FeatureProps<T extends FlagValue> {
 
 /**
  * Returns a domain scoped or the default OpenFeature client with the given context.
- * @param {string} clientDomain The domain of the OpenFeature client.
+ * @param {string} domain The domain of the OpenFeature client.
  * @param {EvaluationContext} context The evaluation context of the client.
  * @returns {Client} The OpenFeature client.
  */
diff --git a/packages/nest/tsconfig.json b/packages/nest/tsconfig.json
index 7fba90b7..40a13720 100644
--- a/packages/nest/tsconfig.json
+++ b/packages/nest/tsconfig.json
@@ -64,7 +64,7 @@
     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
-    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+    "stripInternal": true,                               /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
     // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
index ce6ea3fe..d61d16d9 100644
--- a/packages/react/tsconfig.json
+++ b/packages/react/tsconfig.json
@@ -61,7 +61,7 @@
     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
-    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+    "stripInternal": true,                               /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
     // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
diff --git a/packages/server/README.md b/packages/server/README.md
index 50d78659..7e0bde7b 100644
--- a/packages/server/README.md
+++ b/packages/server/README.md
@@ -279,7 +279,6 @@ import {
   Logger,
   Provider,
   ProviderEventEmitter,
-  ProviderStatus,
   ResolutionDetails
 } from '@openfeature/server-sdk';
 
@@ -305,8 +304,6 @@ class MyProvider implements Provider {
     // code to evaluate an object
   }
 
-  status?: ProviderStatus | undefined;
-
   // implement with "new OpenFeatureEventEmitter()", and use "emit()" to emit events
   events?: ProviderEventEmitter<AnyProviderEvent> | undefined; 
 
diff --git a/packages/server/src/client/client.ts b/packages/server/src/client/client.ts
index cec24693..1cce15bd 100644
--- a/packages/server/src/client/client.ts
+++ b/packages/server/src/client/client.ts
@@ -1,5 +1,12 @@
-import { ClientMetadata, EvaluationLifeCycle, Eventing, ManageContext, ManageLogger } from '@openfeature/core';
+import {
+  ClientMetadata,
+  EvaluationLifeCycle,
+  Eventing,
+  ManageContext,
+  ManageLogger,
+} from '@openfeature/core';
 import { Features } from '../evaluation';
+import { ProviderStatus } from '../provider';
 
 export interface Client
   extends EvaluationLifeCycle<Client>,
@@ -8,4 +15,8 @@ export interface Client
     ManageLogger<Client>,
     Eventing {
   readonly metadata: ClientMetadata;
+  /**
+   * Returns the status of the associated provider.
+   */
+  readonly providerStatus: ProviderStatus;
 }
diff --git a/packages/server/src/client/open-feature-client.ts b/packages/server/src/client/open-feature-client.ts
index 1e6e5433..6c3f9d83 100644
--- a/packages/server/src/client/open-feature-client.ts
+++ b/packages/server/src/client/open-feature-client.ts
@@ -11,6 +11,8 @@ import {
   Logger,
   ManageContext,
   OpenFeatureError,
+  ProviderFatalError,
+  ProviderNotReadyError,
   ResolutionDetails,
   SafeLogger,
   StandardResolutionReasons,
@@ -21,7 +23,7 @@ import { ProviderEvents } from '../events';
 import { InternalEventEmitter } from '../events/internal/internal-event-emitter';
 import { Hook } from '../hooks';
 import { OpenFeature } from '../open-feature';
-import { Provider } from '../provider';
+import { Provider, ProviderStatus } from '../provider';
 import { Client } from './client';
 
 type OpenFeatureClientOptions = {
@@ -42,6 +44,7 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     // we always want the client to use the current provider,
     // so pass a function to always access the currently registered one.
     private readonly providerAccessor: () => Provider,
+    private readonly providerStatusAccessor: () => ProviderStatus,
     private readonly emitterAccessor: () => InternalEventEmitter,
     private readonly globalLogger: () => Logger,
     private readonly options: OpenFeatureClientOptions,
@@ -60,9 +63,13 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     };
   }
 
+  get providerStatus(): ProviderStatus {
+    return this.providerStatusAccessor();
+  }
+
   addHandler(eventType: ProviderEvents, handler: EventHandler): void {
     this.emitterAccessor().addHandler(eventType, handler);
-    const shouldRunNow = statusMatchesEvent(eventType, this._provider.status);
+    const shouldRunNow = statusMatchesEvent(eventType, this._providerStatus);
 
     if (shouldRunNow) {
       // run immediately, we're in the matching state
@@ -255,6 +262,13 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     try {
       const frozenContext = await this.beforeHooks(allHooks, hookContext, options);
 
+      // short circuit evaluation entirely if provider is in a bad state
+      if (this.providerStatus === ProviderStatus.NOT_READY) {
+        throw new ProviderNotReadyError('provider has not yet initialized');
+      } else if (this.providerStatus === ProviderStatus.FATAL) {
+        throw new ProviderFatalError('provider is in an irrecoverable error state');
+      }
+
       // run the referenced resolver, binding the provider.
       const resolution = await resolver.call(this._provider, flagKey, defaultValue, frozenContext, this._logger);
 
@@ -348,6 +362,10 @@ export class OpenFeatureClient implements Client, ManageContext<OpenFeatureClien
     return this.providerAccessor();
   }
 
+  private get _providerStatus(): ProviderStatus {
+    return this.providerStatusAccessor();
+  }
+
   private get _logger() {
     return this._clientLogger || this.globalLogger();
   }
diff --git a/packages/server/src/open-feature.ts b/packages/server/src/open-feature.ts
index 1af2d48a..7d8c73cf 100644
--- a/packages/server/src/open-feature.ts
+++ b/packages/server/src/open-feature.ts
@@ -1,20 +1,22 @@
-import { NOOP_PROVIDER, Provider } from './provider';
 import {
+  EvaluationContext,
   ManageContext,
   OpenFeatureCommonAPI,
-  EvaluationContext,
+  ProviderWrapper,
   objectOrUndefined,
   stringOrUndefined,
 } from '@openfeature/core';
+import { Client, OpenFeatureClient } from './client';
+import { OpenFeatureEventEmitter } from './events';
+import { Hook } from './hooks';
+import { NOOP_PROVIDER, Provider, ProviderStatus } from './provider';
 import {
   ManageTransactionContextPropagator,
   NOOP_TRANSACTION_CONTEXT_PROPAGATOR,
   TransactionContext,
   TransactionContextPropagator,
 } from './transaction-context';
-import { Client, OpenFeatureClient } from './client';
-import { OpenFeatureEventEmitter } from './events';
-import { Hook } from './hooks';
+import { ServerProviderStatus } from '@openfeature/core';
 
 // use a symbol as a key for the global singleton
 const GLOBAL_OPENFEATURE_API_KEY = Symbol.for('@openfeature/js-sdk/api');
@@ -25,11 +27,13 @@ type OpenFeatureGlobal = {
 const _globalThis = globalThis as OpenFeatureGlobal;
 
 export class OpenFeatureAPI
-  extends OpenFeatureCommonAPI<Provider, Hook>
-  implements ManageContext<OpenFeatureAPI>, ManageTransactionContextPropagator<OpenFeatureCommonAPI<Provider>>
+  extends OpenFeatureCommonAPI<ServerProviderStatus, Provider, Hook>
+  implements ManageContext<OpenFeatureAPI>, ManageTransactionContextPropagator<OpenFeatureCommonAPI<ServerProviderStatus, Provider>>
 {
-  protected _events = new OpenFeatureEventEmitter();
-  protected _defaultProvider: Provider = NOOP_PROVIDER;
+  protected _statusEnumType: typeof ProviderStatus = ProviderStatus;
+  protected _apiEmitter = new OpenFeatureEventEmitter();
+  protected _defaultProvider: ProviderWrapper<Provider, ServerProviderStatus> = new ProviderWrapper(NOOP_PROVIDER, ProviderStatus.NOT_READY, this._statusEnumType);
+  protected _domainScopedProviders: Map<string, ProviderWrapper<Provider, ServerProviderStatus>> = new Map();
   protected _createEventEmitter = () => new OpenFeatureEventEmitter();
 
   private _transactionContextPropagator: TransactionContextPropagator = NOOP_TRANSACTION_CONTEXT_PROPAGATOR;
@@ -54,6 +58,14 @@ export class OpenFeatureAPI
     return instance;
   }
 
+  private getProviderStatus(domain?: string): ProviderStatus {
+    if (!domain) {
+      return this._defaultProvider.status;
+    }
+
+    return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
+  }
+
   setContext(context: EvaluationContext): this {
     this._context = context;
     return this;
@@ -112,6 +124,7 @@ export class OpenFeatureAPI
 
     return new OpenFeatureClient(
       () => this.getProviderForClient(domain),
+      () => this.getProviderStatus(domain),
       () => this.buildAndCacheEventEmitterForClient(domain),
       () => this._logger,
       { domain, version },
@@ -129,7 +142,7 @@ export class OpenFeatureAPI
 
   setTransactionContextPropagator(
     transactionContextPropagator: TransactionContextPropagator,
-  ): OpenFeatureCommonAPI<Provider> {
+  ): OpenFeatureCommonAPI<ServerProviderStatus, Provider> {
     const baseMessage = 'Invalid TransactionContextPropagator, will not be set: ';
     if (typeof transactionContextPropagator?.getTransactionContext !== 'function') {
       this._logger.error(`${baseMessage}: getTransactionContext is not a function.`);
diff --git a/packages/server/src/provider/no-op-provider.ts b/packages/server/src/provider/no-op-provider.ts
index 7a056f71..c8db369c 100644
--- a/packages/server/src/provider/no-op-provider.ts
+++ b/packages/server/src/provider/no-op-provider.ts
@@ -1,4 +1,4 @@
-import { ResolutionDetails, JsonValue, ProviderStatus } from '@openfeature/core';
+import { JsonValue, ResolutionDetails } from '@openfeature/core';
 import { Provider } from './provider';
 
 const REASON_NO_OP = 'No-op';
@@ -11,16 +11,6 @@ class NoopFeatureProvider implements Provider {
     name: 'No-op Provider',
   } as const;
 
-  get status(): ProviderStatus {
-    /**
-     * This is due to the NoopProvider not being a real provider.
-     * We do not want it to trigger the Ready event handlers, so we never set this to ready.
-     * With the NoopProvider assigned, the client can be assumed to be uninitialized.
-     * https://github.com/open-feature/js-sdk/pull/429#discussion_r1202642654
-     */
-    return ProviderStatus.NOT_READY;
-  }
-
   resolveBooleanEvaluation(_: string, defaultValue: boolean): Promise<ResolutionDetails<boolean>> {
     return this.noOp(defaultValue);
   }
diff --git a/packages/server/src/provider/provider.ts b/packages/server/src/provider/provider.ts
index 6e9ab0ce..63173d3d 100644
--- a/packages/server/src/provider/provider.ts
+++ b/packages/server/src/provider/provider.ts
@@ -1,13 +1,15 @@
-import { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/core';
+import { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails, ServerProviderStatus } from '@openfeature/core';
 import { Hook } from '../hooks';
 
+export { ServerProviderStatus as ProviderStatus };
+
 /**
  * Interface that providers must implement to resolve flag values for their particular
  * backend or vendor.
  *
  * Implementation for resolving all the required flag types must be defined.
  */
-export interface Provider extends CommonProvider {
+export interface Provider extends CommonProvider<ServerProviderStatus> {
   /**
    * A provider hook exposes a mechanism for provider authors to register hooks
    * to tap into various stages of the flag evaluation lifecycle. These hooks can
diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json
index 12f2abfc..6f6a4a15 100644
--- a/packages/server/tsconfig.json
+++ b/packages/server/tsconfig.json
@@ -58,7 +58,7 @@
     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
-    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+    "stripInternal": true,                               /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
     // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
diff --git a/packages/shared/src/errors/index.ts b/packages/shared/src/errors/index.ts
index 6a58e0c8..6cde15e2 100644
--- a/packages/shared/src/errors/index.ts
+++ b/packages/shared/src/errors/index.ts
@@ -6,3 +6,4 @@ export * from './targeting-key-missing-error';
 export * from './invalid-context-error';
 export * from './open-feature-error-abstract';
 export * from './provider-not-ready-error';
+export * from './provider-fatal-error';
diff --git a/packages/shared/src/errors/provider-fatal-error.ts b/packages/shared/src/errors/provider-fatal-error.ts
new file mode 100644
index 00000000..923a8470
--- /dev/null
+++ b/packages/shared/src/errors/provider-fatal-error.ts
@@ -0,0 +1,12 @@
+import { OpenFeatureError } from './open-feature-error-abstract';
+import { ErrorCode } from '../evaluation';
+
+export class ProviderFatalError extends OpenFeatureError {
+  code: ErrorCode;
+  constructor(message?: string, options?: ErrorOptions) {
+    super(message, options);
+    Object.setPrototypeOf(this, ProviderFatalError.prototype);
+    this.name = 'ProviderFatalError';
+    this.code = ErrorCode.PROVIDER_FATAL;
+  }
+}
diff --git a/packages/shared/src/evaluation/evaluation.ts b/packages/shared/src/evaluation/evaluation.ts
index 55c30713..c629a0af 100644
--- a/packages/shared/src/evaluation/evaluation.ts
+++ b/packages/shared/src/evaluation/evaluation.ts
@@ -87,6 +87,11 @@ export enum ErrorCode {
    */
   PROVIDER_NOT_READY = 'PROVIDER_NOT_READY',
 
+  /**
+   * The provider has entered an irrecoverable error state.
+   */
+  PROVIDER_FATAL = 'PROVIDER_FATAL',
+
   /**
    * The flag could not be found.
    */
diff --git a/packages/shared/src/events/event-utils.ts b/packages/shared/src/events/event-utils.ts
index d538c85d..3f6cf3fa 100644
--- a/packages/shared/src/events/event-utils.ts
+++ b/packages/shared/src/events/event-utils.ts
@@ -1,20 +1,22 @@
-import { ProviderStatus } from '../provider';
+import { AllProviderStatus, ClientProviderStatus, ServerProviderStatus } from '../provider';
 import { AllProviderEvents, AnyProviderEvent } from './events';
 
 const eventStatusMap = {
-    [ProviderStatus.READY]: AllProviderEvents.Ready,
-    [ProviderStatus.ERROR]: AllProviderEvents.Error,
-    [ProviderStatus.STALE]: AllProviderEvents.Stale,
-    [ProviderStatus.NOT_READY]: undefined,
+    [AllProviderStatus.READY]: AllProviderEvents.Ready,
+    [AllProviderStatus.ERROR]: AllProviderEvents.Error,
+    [AllProviderStatus.FATAL]: AllProviderEvents.Error,
+    [AllProviderStatus.STALE]: AllProviderEvents.Stale,
+    [AllProviderStatus.RECONCILING]: AllProviderEvents.Reconciling,
+    [AllProviderStatus.NOT_READY]: undefined,
 };
 
 /**
  * Returns true if the provider's status corresponds to the event.
  * If the provider's status is not defined, it matches READY.
  * @param {AnyProviderEvent} event event to match
- * @param {ProviderStatus} status  status of provider
+ * @param {ClientProviderStatus | ServerProviderStatus} status  status of provider
  * @returns {boolean} boolean indicating if the provider status corresponds to the event.
  */
-export const statusMatchesEvent = <T extends AnyProviderEvent>(event: T, status?: ProviderStatus): boolean => {
+export const statusMatchesEvent = <T extends AnyProviderEvent>(event: T, status?: ClientProviderStatus | ServerProviderStatus): boolean => {
     return (!status && event === AllProviderEvents.Ready) || eventStatusMap[status!] === event;
 };
\ No newline at end of file
diff --git a/packages/shared/src/events/eventing.ts b/packages/shared/src/events/eventing.ts
index 78bccc01..c17e7457 100644
--- a/packages/shared/src/events/eventing.ts
+++ b/packages/shared/src/events/eventing.ts
@@ -1,3 +1,4 @@
+import { ErrorCode } from '../evaluation';
 import { AllProviderEvents, AnyProviderEvent } from './events';
 
 export type EventMetadata = {
@@ -18,9 +19,11 @@ type CommonEventProps = {
   metadata?: EventMetadata;
 };
 
+
 export type ReadyEvent = CommonEventProps;
 export type ErrorEvent = CommonEventProps;
 export type StaleEvent = CommonEventProps;
+export type ReconcilingEvent = CommonEventProps & { errorCode: ErrorCode };
 export type ConfigChangeEvent = CommonEventProps & { flagsChanged?: string[] };
 
 type EventMap = {
@@ -29,6 +32,7 @@ type EventMap = {
   [AllProviderEvents.Stale]: StaleEvent;
   [AllProviderEvents.ContextChanged]: CommonEventProps;
   [AllProviderEvents.ConfigurationChanged]: ConfigChangeEvent;
+  [AllProviderEvents.Reconciling]: ReconcilingEvent;
 };
 
 export type EventContext<  U extends Record<string, unknown> = Record<string, unknown>
diff --git a/packages/shared/src/events/events.ts b/packages/shared/src/events/events.ts
index e6101e97..98aa0c74 100644
--- a/packages/shared/src/events/events.ts
+++ b/packages/shared/src/events/events.ts
@@ -51,6 +51,11 @@ export enum ClientProviderEvents {
    */
   ContextChanged = 'PROVIDER_CONTEXT_CHANGED',
 
+  /**
+   * The context associated with the provider has changed, and the provider has not yet reconciled its associated state.
+   */
+  Reconciling = 'PROVIDER_RECONCILING',
+
   /**
    * The provider's cached state is no longer valid and may not be up-to-date with the source of truth.
    */
@@ -64,6 +69,6 @@ export { ClientProviderEvents as AllProviderEvents };
 
 /**
  * A type representing any possible ProviderEvent (server or client side).
- * If you are implementing a hook or provider, you probably want to import `ProviderEvents` from the respective SDK.
+ * In most cases, you probably want to import `ProviderEvents` from the respective SDK.
  */
 export type AnyProviderEvent = ServerProviderEvents | ClientProviderEvents;
diff --git a/packages/shared/src/events/generic-event-emitter.ts b/packages/shared/src/events/generic-event-emitter.ts
index c80837db..c3c0c07f 100644
--- a/packages/shared/src/events/generic-event-emitter.ts
+++ b/packages/shared/src/events/generic-event-emitter.ts
@@ -18,6 +18,7 @@ export abstract class GenericEventEmitter<E extends AnyProviderEvent, Additional
     [AllProviderEvents.Ready]: new WeakMap<EventHandler, EventHandler[]>(),
     [AllProviderEvents.Error]: new WeakMap<EventHandler, EventHandler[]>(),
     [AllProviderEvents.Stale]: new WeakMap<EventHandler, EventHandler[]>(),
+    [AllProviderEvents.Reconciling]: new WeakMap<EventHandler, EventHandler[]>(),
   };
   private _eventLogger?: Logger;
 
diff --git a/packages/shared/src/open-feature.ts b/packages/shared/src/open-feature.ts
index 009eddc5..7248dde9 100644
--- a/packages/shared/src/open-feature.ts
+++ b/packages/shared/src/open-feature.ts
@@ -1,5 +1,5 @@
-import { GeneralError } from './errors';
-import { EvaluationContext } from './evaluation';
+import { GeneralError, OpenFeatureError } from './errors';
+import { ErrorCode, EvaluationContext } from './evaluation';
 import {
   AllProviderEvents,
   AnyProviderEvent,
@@ -12,23 +12,82 @@ import {
 import { isDefined } from './filter';
 import { BaseHook, EvaluationLifeCycle } from './hooks';
 import { DefaultLogger, Logger, ManageLogger, SafeLogger } from './logger';
-import { CommonProvider, ProviderMetadata, ProviderStatus } from './provider';
+import { ClientProviderStatus, CommonProvider, ProviderMetadata, ServerProviderStatus } from './provider';
 import { objectOrUndefined, stringOrUndefined } from './type-guards';
 import { Paradigm } from './types';
 
-export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProvider, H extends BaseHook = BaseHook>
-  implements Eventing, EvaluationLifeCycle<OpenFeatureCommonAPI<P>>, ManageLogger<OpenFeatureCommonAPI<P>>
+type AnyProviderStatus = ClientProviderStatus | ServerProviderStatus;
+
+/**
+ * A provider and its current status.
+ * For internal use only.
+ */
+export class ProviderWrapper<P extends CommonProvider<AnyProviderStatus>, S extends AnyProviderStatus> {
+  private _pendingContextChanges = 0;
+
+  constructor(private _provider: P, private _status: S, _statusEnumType: typeof ClientProviderStatus | typeof ServerProviderStatus) {
+    // update the providers status with events
+    _provider.events?.addHandler(AllProviderEvents.Ready, () => {
+      // These casts are due to the face we don't "know" what status enum we are dealing with here (client or server).
+      // We could abstract this an implement it in the client/server libs to fix this, but the value is low.
+      this._status = _statusEnumType.READY as S;
+    });
+    _provider.events?.addHandler(AllProviderEvents.Stale, () => {
+      this._status = _statusEnumType.STALE as S;
+    });
+    _provider.events?.addHandler(AllProviderEvents.Error, (details) => {
+      if (details?.errorCode === ErrorCode.PROVIDER_FATAL) {
+        this._status = _statusEnumType.FATAL as S;
+      } else {
+        this._status = _statusEnumType.ERROR as S;
+      }
+    });
+  }
+
+  get provider(): P {
+    return this._provider;
+  }
+
+  set provider(provider: P) {
+    this._provider = provider;
+  } 
+
+  get status(): S {
+    return this._status;
+  }
+
+  set status(status: S) {
+    this._status = status;
+  }
+
+  get allContextChangesSettled() {
+    return this._pendingContextChanges === 0;
+  }
+
+  incrementPendingContextChanges() {
+    this._pendingContextChanges++;
+  }
+
+  decrementPendingContextChanges() {
+    this._pendingContextChanges--;
+  }
+}
+
+export abstract class OpenFeatureCommonAPI<S extends AnyProviderStatus, P extends CommonProvider<S> = CommonProvider<S>, H extends BaseHook = BaseHook>
+  implements Eventing, EvaluationLifeCycle<OpenFeatureCommonAPI<S, P>>, ManageLogger<OpenFeatureCommonAPI<S, P>>
 {
+  // accessor for the type of the ProviderStatus enum (client or server)
+  protected abstract readonly _statusEnumType: typeof ClientProviderStatus | typeof ServerProviderStatus;
   protected abstract _createEventEmitter(): GenericEventEmitter<AnyProviderEvent>;
-  protected abstract _defaultProvider: P;
-  protected abstract readonly _events: GenericEventEmitter<AnyProviderEvent>;
+  protected abstract _defaultProvider: ProviderWrapper<P, AnyProviderStatus>;
+  protected abstract readonly _domainScopedProviders: Map<string, ProviderWrapper<P, AnyProviderStatus>>;
+  protected abstract readonly _apiEmitter: GenericEventEmitter<AnyProviderEvent>;
 
   protected _hooks: H[] = [];
   protected _context: EvaluationContext = {};
   protected _logger: Logger = new DefaultLogger();
 
   private readonly _clientEventHandlers: Map<string | undefined, [AnyProviderEvent, EventHandler][]> = new Map();
-  protected _domainScopedProviders: Map<string, P> = new Map();
   protected _domainScopedContext: Map<string, EvaluationContext> = new Map();
   protected _clientEvents: Map<string | undefined, GenericEventEmitter<AnyProviderEvent>> = new Map();
   protected _runsOn: Paradigm;
@@ -84,8 +143,9 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
   addHandler<T extends AnyProviderEvent>(eventType: T, handler: EventHandler): void {
     [...new Map([[undefined, this._defaultProvider]]), ...this._domainScopedProviders].forEach((keyProviderTuple) => {
       const domain = keyProviderTuple[0];
-      const provider = keyProviderTuple[1];
-      const shouldRunNow = statusMatchesEvent(eventType, keyProviderTuple[1].status);
+      const provider = keyProviderTuple[1].provider;
+      const status = keyProviderTuple[1].status;
+      const shouldRunNow = statusMatchesEvent(eventType, status);
 
       if (shouldRunNow) {
         // run immediately, we're in the matching state
@@ -97,7 +157,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
       }
     });
 
-    this._events.addHandler(eventType, handler);
+    this._apiEmitter.addHandler(eventType, handler);
   }
 
   /**
@@ -106,14 +166,14 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
    * @param {EventHandler} handler The handler to remove for the provider event type
    */
   removeHandler<T extends AnyProviderEvent>(eventType: T, handler: EventHandler): void {
-    this._events.removeHandler(eventType, handler);
+    this._apiEmitter.removeHandler(eventType, handler);
   }
 
   /**
    * Removes all event handlers.
    */
   clearHandlers(): void {
-    this._events.removeAllHandlers();
+    this._apiEmitter.removeAllHandlers();
   }
 
   /**
@@ -122,7 +182,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
    * @returns {EventHandler[]} The handlers currently attached to the given provider event type
    */
   getHandlers<T extends AnyProviderEvent>(eventType: T): EventHandler[] {
-    return this._events.getHandlers(eventType);
+    return this._apiEmitter.getHandlers(eventType);
   }
 
   /**
@@ -204,27 +264,28 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
 
     const emitters = this.getAssociatedEventEmitters(domain);
 
-    // warn of improper implementations
-    if (typeof provider.initialize === 'function' && provider.status === undefined) {
-      const activeLogger = this._logger || console;
-      activeLogger.warn(
-        `Provider ${providerName} implements 'initialize' but not 'status'. Please implement 'status'.`,
-      );
-    }
-
     let initializationPromise: Promise<void> | void = undefined;
+    const wrappedProvider = new ProviderWrapper<P, AnyProviderStatus>(provider, this._statusEnumType.NOT_READY, this._statusEnumType);
 
-    if (provider?.status === ProviderStatus.NOT_READY && typeof provider.initialize === 'function') {
+    // initialize the provider if it implements "initialize" and it's not already registered
+    if (typeof provider.initialize === 'function' && !this.allProviders.includes(provider)) {
       initializationPromise = provider
         .initialize?.(domain ? this._domainScopedContext.get(domain) ?? this._context : this._context)
         ?.then(() => {
+          wrappedProvider.status = this._statusEnumType.READY;
           // fetch the most recent event emitters, some may have been added during init
           this.getAssociatedEventEmitters(domain).forEach((emitter) => {
             emitter?.emit(AllProviderEvents.Ready, { clientName: domain, domain, providerName });
           });
-          this._events?.emit(AllProviderEvents.Ready, { clientName: domain, domain, providerName });
+          this._apiEmitter?.emit(AllProviderEvents.Ready, { clientName: domain, domain, providerName });
         })
         ?.catch((error) => {
+          // if this is a fatal error, transition to FATAL status
+          if ((error as OpenFeatureError)?.code === ErrorCode.PROVIDER_FATAL) {
+            wrappedProvider.status = this._statusEnumType.FATAL;
+          } else {
+            wrappedProvider.status = this._statusEnumType.ERROR;
+          }
           this.getAssociatedEventEmitters(domain).forEach((emitter) => {
             emitter?.emit(AllProviderEvents.Error, {
               clientName: domain,
@@ -233,7 +294,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
               message: error?.message,
             });
           });
-          this._events?.emit(AllProviderEvents.Error, {
+          this._apiEmitter?.emit(AllProviderEvents.Error, {
             clientName: domain,
             domain,
             providerName,
@@ -243,23 +304,26 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
           throw error;
         });
     } else {
+      wrappedProvider.status = this._statusEnumType.READY;
       emitters.forEach((emitter) => {
         emitter?.emit(AllProviderEvents.Ready, { clientName: domain, domain, providerName });
       });
-      this._events?.emit(AllProviderEvents.Ready, { clientName: domain, domain, providerName });
+      this._apiEmitter?.emit(AllProviderEvents.Ready, { clientName: domain, domain, providerName });
     }
 
     if (domain) {
-      this._domainScopedProviders.set(domain, provider);
+      this._domainScopedProviders.set(domain, wrappedProvider);
     } else {
-      this._defaultProvider = provider;
+      this._defaultProvider = wrappedProvider;
     }
 
     this.transferListeners(oldProvider, provider, domain, emitters);
 
     // Do not close a provider that is bound to any client
-    if (![...this._domainScopedProviders.values(), this._defaultProvider].includes(oldProvider)) {
-      oldProvider?.onClose?.();
+    if (!this.allProviders.includes(oldProvider)) {
+      oldProvider?.onClose?.()?.catch((err: Error | undefined) => {
+        this._logger.error(`error closing provider: ${err?.message}, ${err?.stack}`);
+      });
     }
 
     return initializationPromise;
@@ -267,10 +331,10 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
 
   protected getProviderForClient(domain?: string): P {
     if (!domain) {
-      return this._defaultProvider;
+      return this._defaultProvider.provider;
     }
 
-    return this._domainScopedProviders.get(domain) ?? this._defaultProvider;
+    return this._domainScopedProviders.get(domain)?.provider ?? this._defaultProvider.provider;
   }
 
   protected buildAndCacheEventEmitterForClient(domain?: string): GenericEventEmitter<AnyProviderEvent> {
@@ -332,7 +396,7 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
         emitters.forEach((emitter) => {
           emitter?.emit(eventType, { ...details, clientName: domain, domain, providerName: newProvider.metadata.name });
         });
-        this._events.emit(eventType, {
+        this._apiEmitter.emit(eventType, {
           ...details,
           clientName: domain,
           domain,
@@ -349,19 +413,19 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
 
   async close(): Promise<void> {
     try {
-      await this?._defaultProvider?.onClose?.();
+      await this?._defaultProvider.provider?.onClose?.();
     } catch (err) {
-      this.handleShutdownError(this._defaultProvider, err);
+      this.handleShutdownError(this._defaultProvider.provider, err);
     }
 
-    const providers = Array.from(this._domainScopedProviders);
+    const wrappers = Array.from(this._domainScopedProviders);
 
     await Promise.all(
-      providers.map(async ([, provider]) => {
+      wrappers.map(async ([, wrapper]) => {
         try {
-          await provider.onClose?.();
+          await wrapper?.provider.onClose?.();
         } catch (err) {
-          this.handleShutdownError(provider, err);
+          this.handleShutdownError(wrapper?.provider, err);
         }
       }),
     );
@@ -374,10 +438,17 @@ export abstract class OpenFeatureCommonAPI<P extends CommonProvider = CommonProv
       this._logger.error('Unable to cleanly close providers. Resetting to the default configuration.');
     } finally {
       this._domainScopedProviders.clear();
-      this._defaultProvider = defaultProvider;
+      this._defaultProvider = new ProviderWrapper<P, AnyProviderStatus>(defaultProvider, this._statusEnumType.NOT_READY, this._statusEnumType);
     }
   }
 
+  private get allProviders(): P[] {
+    return [
+      ...[...this._domainScopedProviders.values()].map((wrappers) => wrappers.provider),
+      this._defaultProvider.provider,
+    ];
+  }
+
   private handleShutdownError(provider: P, err: unknown) {
     this._logger.error(`Error during shutdown of provider ${provider.metadata.name}: ${err}`);
     this._logger.error((err as Error)?.stack);
diff --git a/packages/shared/src/provider/provider.ts b/packages/shared/src/provider/provider.ts
index 7ef3b6b0..364c1ce8 100644
--- a/packages/shared/src/provider/provider.ts
+++ b/packages/shared/src/provider/provider.ts
@@ -2,10 +2,15 @@ import { EvaluationContext } from '../evaluation';
 import { AnyProviderEvent, ProviderEventEmitter } from '../events';
 import { Metadata, Paradigm } from '../types';
 
+// TODO: with TypeScript 5+, we can use computed string properties,
+// so we can extract all of these into a shared set of string consts and use that in both enums
+// for now we have duplicated them.
+
 /**
  * The state of the provider.
+ * Note that the provider's state is handled by the SDK.
  */
-export enum ProviderStatus {
+export enum ServerProviderStatus {
   /**
    * The provider has not been initialized and cannot yet evaluate flags.
    */
@@ -25,8 +30,55 @@ export enum ProviderStatus {
    * The provider's cached state is no longer valid and may not be up-to-date with the source of truth.
    */
   STALE = 'STALE',
+
+  /**
+   * The provider has entered an irrecoverable error state.
+   */
+  FATAL = 'FATAL',
 }
 
+/**
+ * The state of the provider.
+ * Note that the provider's state is handled by the SDK.
+ */
+export enum ClientProviderStatus {
+  /**
+   * The provider has not been initialized and cannot yet evaluate flags.
+   */
+  NOT_READY = 'NOT_READY',
+
+  /**
+   * The provider is ready to resolve flags.
+   */
+  READY = 'READY',
+
+  /**
+   * The provider is in an error state and unable to evaluate flags.
+   */
+  ERROR = 'ERROR',
+
+  /**
+   * The provider's cached state is no longer valid and may not be up-to-date with the source of truth.
+   */
+  STALE = 'STALE',
+
+  /**
+   * The provider has entered an irrecoverable error state.
+   */
+  FATAL = 'FATAL',
+
+  /**
+   * The provider is reconciling its state with a context change.
+   */
+  RECONCILING = 'RECONCILING',
+}
+
+/**
+ * A type representing any possible ProviderStatus (server or client side).
+ * In most cases, you probably want to import `ProviderStatus` from the respective SDK.
+ */
+export { ClientProviderStatus as AllProviderStatus };
+
 /**
  * Static data about the provider.
  */
@@ -34,7 +86,7 @@ export interface ProviderMetadata extends Metadata {
   readonly name: string;
 }
 
-export interface CommonProvider {
+export interface CommonProvider<S extends ClientProviderStatus | ServerProviderStatus> {
   readonly metadata: ProviderMetadata;
 
   /**
@@ -43,15 +95,14 @@ export interface CommonProvider {
    */
   readonly runsOn?: Paradigm;
 
+  // TODO: in the future we could make this a never to force provider to remove it.
   /**
+   * @deprecated the SDK now maintains the provider's state; there's no need for providers to implement this field.
    * Returns a representation of the current readiness of the provider.
-   * If the provider needs to be initialized, it should return {@link ProviderStatus.READY}.
-   * If the provider is in an error state, it should return {@link ProviderStatus.ERROR}.
-   * If the provider is functioning normally, it should return {@link ProviderStatus.NOT_READY}.
    * 
    * _Providers which do not implement this method are assumed to be ready immediately._
    */
-  readonly status?: ProviderStatus;
+  readonly status?: S;
 
   /**
    * An event emitter for ProviderEvents.
@@ -67,7 +118,7 @@ export interface CommonProvider {
 
   /**
    * A function used to setup the provider.
-   * Called by the SDK after the provider is set if the provider's status is {@link ProviderStatus.NOT_READY}.
+   * Called by the SDK after the provider is set if the provider's status is NOT_READY.
    * When the returned promise resolves, the SDK fires the ProviderEvents.Ready event.
    * If the returned promise rejects, the SDK fires the ProviderEvents.Error event.
    * Use this function to perform any context-dependent setup within the provider.
diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json
index 7729e02b..c43abeb0 100644
--- a/packages/shared/tsconfig.json
+++ b/packages/shared/tsconfig.json
@@ -56,7 +56,7 @@
     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
-    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+    "stripInternal": true,                               /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
     // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
diff --git a/tsconfig.json b/tsconfig.json
index 6cbf4cd6..ac3ccc66 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -59,7 +59,7 @@
     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
-    // "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+    "stripInternal": true,                               /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
     // "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */


Test patch:
diff --git a/packages/client/e2e/step-definitions/evaluation.spec.ts b/packages/client/e2e/step-definitions/evaluation.spec.ts
index f03e7680..94e89891 100644
--- a/packages/client/e2e/step-definitions/evaluation.spec.ts
+++ b/packages/client/e2e/step-definitions/evaluation.spec.ts
@@ -1,13 +1,13 @@
-import { defineFeature, loadFeature } from 'jest-cucumber';
 import {
-  JsonValue,
-  JsonObject,
-  EvaluationDetails,
   EvaluationContext,
+  EvaluationDetails,
+  JsonObject,
+  JsonValue,
   ResolutionDetails,
   StandardResolutionReasons,
 } from '@openfeature/core';
-import { OpenFeature, ProviderEvents, InMemoryProvider } from '../../src';
+import { defineFeature, loadFeature } from 'jest-cucumber';
+import { InMemoryProvider, OpenFeature } from '../../src';
 import flagConfiguration from './flags-config';
 // load the feature file.
 const feature = loadFeature('packages/client/e2e/features/evaluation.feature');
@@ -18,15 +18,12 @@ const client = OpenFeature.getClient();
 const givenAnOpenfeatureClientIsRegisteredWithCacheDisabled = (
   given: (stepMatcher: string, stepDefinitionCallback: () => void) => void
 ) => {
-  OpenFeature.setProvider(new InMemoryProvider(flagConfiguration));
   given('a provider is registered with cache disabled', () => undefined);
 };
 
 defineFeature(feature, (test) => {
-  beforeAll((done) => {
-    client.addHandler(ProviderEvents.Ready, async () => {
-      done();
-    });
+  beforeAll(async () => {
+    await OpenFeature.setProvider(new InMemoryProvider(flagConfiguration));
   });
 
   afterAll(async () => {
diff --git a/packages/client/test/client.spec.ts b/packages/client/test/client.spec.ts
index 3088e5cf..34a7fdf3 100644
--- a/packages/client/test/client.spec.ts
+++ b/packages/client/test/client.spec.ts
@@ -3,12 +3,14 @@ import {
   ErrorCode,
   EvaluationDetails,
   FlagNotFoundError,
+  GeneralError,
   JsonArray,
   JsonObject,
   JsonValue,
   OpenFeature,
   OpenFeatureClient,
   Provider,
+  ProviderFatalError,
   ProviderStatus,
   ResolutionDetails,
   StandardResolutionReasons,
@@ -385,6 +387,83 @@ describe('OpenFeatureClient', () => {
       });
     });
   });
+
+  describe('Requirement 1.7.1, 1.7.3', () => {
+    const initProvider = {
+      metadata: {
+        name: 'initProvider',
+      },
+      initialize: () => {
+        return Promise.resolve();
+      }
+    } as unknown as Provider;
+    it('status must be READY if init resolves', async () => {
+      await OpenFeature.setProviderAndWait('1.7.1, 1.7.3', initProvider);
+      const client = OpenFeature.getClient('1.7.1, 1.7.3');
+      expect(client.providerStatus).toEqual(ProviderStatus.READY);
+    });
+  });
+
+  describe('Requirement 1.7.4', () => {
+    const errorProvider = {
+      metadata: {
+        name: 'errorProvider',
+      },
+      initialize: async () => {
+        return Promise.reject(new GeneralError());
+      }
+    } as unknown as Provider;
+    it('status must be ERROR if init rejects', async () => {
+      await expect(OpenFeature.setProviderAndWait('1.7.4', errorProvider)).rejects.toThrow();
+      const client = OpenFeature.getClient('1.7.4');
+      expect(client.providerStatus).toEqual(ProviderStatus.ERROR);
+    });
+  });
+
+  describe('Requirement 1.7.5, 1.7.6, 1.7.8', () => {
+    const fatalProvider = {
+      metadata: {
+        name: 'fatalProvider',
+      },
+      initialize: () => {
+        return Promise.reject(new ProviderFatalError());
+      }
+    } as unknown as Provider;
+    it('must short circuit and return PROVIDER_FATAL code if provider FATAL', async () => {
+      await expect(OpenFeature.setProviderAndWait('1.7.5, 1.7.6, 1.7.8', fatalProvider)).rejects.toThrow();
+      const client = OpenFeature.getClient('1.7.5, 1.7.6, 1.7.8');
+      expect(client.providerStatus).toEqual(ProviderStatus.FATAL);
+
+      const defaultVal = 'default';
+      const details = await client.getStringDetails('some-flag', defaultVal);
+      expect(details.value).toEqual(defaultVal);
+      expect(details.errorCode).toEqual(ErrorCode.PROVIDER_FATAL);
+    });
+  });
+
+  describe('Requirement 1.7.7', () => {
+    const neverReadyProvider = {
+      metadata: {
+        name: 'fatalProvider',
+      },
+      initialize: () => {
+        return new Promise(() => {
+          return; // promise never resolves
+        });
+      }
+    } as unknown as Provider;
+    it('must short circuit and return PROVIDER_NOT_READY code if provider NOT_READY', async () => {
+      OpenFeature.setProviderAndWait('1.7.7', neverReadyProvider).catch(() => {
+        // do nothing
+      });
+      const defaultVal = 'default';
+      const client = OpenFeature.getClient('1.7.7');
+      expect(client.providerStatus).toEqual(ProviderStatus.NOT_READY);
+      const details = await client.getStringDetails('some-flag', defaultVal);
+      expect(details.value).toEqual(defaultVal);
+      expect(details.errorCode).toEqual(ErrorCode.PROVIDER_NOT_READY);
+    });
+  });
   
   describe('Evaluation details structure', () => {
     const flagKey = 'number-details';
@@ -535,13 +614,22 @@ describe('OpenFeatureClient', () => {
   });
 
   describe('providerStatus', () => {
-    it('should return current provider status', ()=> {
-      OpenFeature.setProvider({ ...MOCK_PROVIDER, status: ProviderStatus.STALE});
-      expect(OpenFeature.getClient().providerStatus).toEqual(ProviderStatus.STALE);
+    it('should return current provider status', (done) => {
+      OpenFeature.setProviderAndWait({
+        ...MOCK_PROVIDER,
+        initialize: () => {
+          return new Promise<void>((resolve) => setTimeout(resolve, 1000));
+        },
+      }).then(() => {
+        expect(OpenFeature.getClient().providerStatus).toEqual(ProviderStatus.READY);
+        done();
+      });
+
+      expect(OpenFeature.getClient().providerStatus).toEqual(ProviderStatus.NOT_READY);
     });
 
-    it('should return READY if not defined', ()=> {
-      OpenFeature.setProvider(MOCK_PROVIDER);
+    it('should return READY if initialize not defined', async () => {
+      await OpenFeature.setProviderAndWait({ ...MOCK_PROVIDER, initialize: undefined });
       expect(OpenFeature.getClient().providerStatus).toEqual(ProviderStatus.READY);
     });
   });
diff --git a/packages/client/test/events.spec.ts b/packages/client/test/events.spec.ts
index a358499c..d4060f73 100644
--- a/packages/client/test/events.spec.ts
+++ b/packages/client/test/events.spec.ts
@@ -21,31 +21,31 @@ class MockProvider implements Provider {
   readonly events?: OpenFeatureEventEmitter;
   readonly runsOn = 'client';
   private hasInitialize: boolean;
+  private hasContextChanged: boolean;
   private failOnInit: boolean;
   private failOnContextChange: boolean;
   private asyncDelay?: number;
   private enableEvents: boolean;
-  status?: ProviderStatus = undefined;
   onContextChange?: () => Promise<void>;
+  initialize?: () => Promise<void>;
 
   constructor(options?: {
     hasInitialize?: boolean;
-    initialStatus?: ProviderStatus;
     asyncDelay?: number;
     enableEvents?: boolean;
     failOnInit?: boolean;
-    noContextChanged?: boolean;
+    hasContextChanged?: boolean;
     failOnContextChange?: boolean;
     name?: string;
   }) {
     this.metadata = { name: options?.name ?? 'mock-provider' };
     this.hasInitialize = options?.hasInitialize ?? true;
-    this.status = options?.initialStatus ?? ProviderStatus.NOT_READY;
+    this.hasContextChanged = options?.hasContextChanged ?? true;
     this.asyncDelay = options?.asyncDelay ?? 0;
     this.enableEvents = options?.enableEvents ?? true;
     this.failOnInit = options?.failOnInit ?? false;
     this.failOnContextChange = options?.failOnContextChange ?? false;
-    if (!options?.noContextChanged) {
+    if (this.hasContextChanged) {
       this.onContextChange = this.changeHandler;
     }
 
@@ -59,14 +59,10 @@ class MockProvider implements Provider {
         if (this.failOnInit) {
           throw new Error(ERR_MESSAGE);
         }
-
-        this.status = ProviderStatus.READY;
       });
     }
   }
 
-  initialize: jest.Mock<Promise<void>, []> | undefined;
-
   resolveBooleanEvaluation(): ResolutionDetails<boolean> {
     throw new Error('Not implemented');
   }
@@ -235,7 +231,6 @@ describe('Events', () => {
     it('anonymous provider with anonymous client should run non-init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
 
@@ -255,7 +250,6 @@ describe('Events', () => {
     it('anonymous provider with anonymous client should run init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
 
@@ -272,7 +266,6 @@ describe('Events', () => {
     it('anonymous provider with named client should run non-init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
       const unboundName = 'some-new-unbound-name';
@@ -293,7 +286,6 @@ describe('Events', () => {
     it('anonymous provider with named client should run init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
       const unboundName = 'some-other-unbound-name';
@@ -334,7 +326,6 @@ describe('Events', () => {
     it('handler added while while provider initializing runs', (done) => {
       const provider = new MockProvider({
         name: 'race',
-        initialStatus: ProviderStatus.NOT_READY,
         asyncDelay: TIMEOUT / 2,
       });
 
@@ -531,35 +522,22 @@ describe('Events', () => {
     describe('API', () => {
       describe('Handlers attached after the provider is already in the associated state, MUST run immediately.', () => {
         it('Ready', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
+          const provider = new MockProvider({ hasInitialize: false });
 
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          OpenFeature.addHandler(ProviderEvents.Ready, () => {
-            done();
+          OpenFeature.setProviderAndWait(domain, provider).then(() => {
+            OpenFeature.addHandler(ProviderEvents.Ready, () => {
+              done();
+            });
           });
         });
 
         it('Error', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.ERROR });
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          OpenFeature.addHandler(ProviderEvents.Error, () => {
-            done();
-          });
-        });
-
-        it('Stale', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.STALE });
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
+          const provider = new MockProvider({ failOnInit: true });
 
-          OpenFeature.addHandler(ProviderEvents.Stale, () => {
-            done();
+          OpenFeature.setProviderAndWait(domain, provider).catch(() => {
+            OpenFeature.addHandler(ProviderEvents.Error, () => {
+              done();
+            });
           });
         });
       });
@@ -568,39 +546,93 @@ describe('Events', () => {
     describe('client', () => {
       describe('Handlers attached after the provider is already in the associated state, MUST run immediately.', () => {
         it('Ready', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
+          const provider = new MockProvider({ hasInitialize: false });
           const client = OpenFeature.getClient(domain);
 
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          client.addHandler(ProviderEvents.Ready, () => {
-            done();
+          OpenFeature.setProviderAndWait(domain, provider).then(() => {
+            client.addHandler(ProviderEvents.Ready, () => {
+              done();
+            });
           });
         });
 
         it('Error', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.ERROR });
+          const provider = new MockProvider({ failOnInit: true });
           const client = OpenFeature.getClient(domain);
 
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          client.addHandler(ProviderEvents.Error, () => {
-            done();
+          OpenFeature.setProviderAndWait(domain, provider).catch(() => {
+            client.addHandler(ProviderEvents.Error, () => {
+              done();
+            });
           });
         });
+      });
+    });
+  });
+
+  describe('Requirement 5.3.4.1', () => {
+    describe('API', () => {
+      describe('provider has context changed handler', () => {
+        it('Reconciling and ContextChanged are emitted', async () => {
+          const provider = new MockProvider({ hasInitialize: false, hasContextChanged: true });
+
+          const handler = jest.fn(() => {});
+
+          await OpenFeature.setProviderAndWait(domain, provider);
+          OpenFeature.addHandler(ProviderEvents.Reconciling, handler);
+          OpenFeature.addHandler(ProviderEvents.ContextChanged, handler);
+          await OpenFeature.setContext(domain, {});
+
+          expect(handler).toHaveBeenCalledTimes(2);
+        });
+      });
+
+      describe('provider has no context changed handler', () => {
+        it('only ContextChanged is emitted', async () => {
+          const provider = new MockProvider({ hasInitialize: false, hasContextChanged: false });
+
+          const handler = jest.fn(() => {});
 
-        it('Stale', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.STALE });
+          await OpenFeature.setProviderAndWait(domain, provider);
+          OpenFeature.addHandler(ProviderEvents.Reconciling, handler); // this should not be called
+          OpenFeature.addHandler(ProviderEvents.ContextChanged, handler);
+          await OpenFeature.setContext(domain, {});
+
+          expect(handler).toHaveBeenCalledTimes(1);
+        });
+      });
+    });
+    
+    describe('client', () => {
+      describe('provider has context changed handler', () => {
+        it('Stale and ContextChanged are emitted', async () => {
+          const provider = new MockProvider({ hasInitialize: false, hasContextChanged: true });
+
+          const handler = jest.fn(() => {});
           const client = OpenFeature.getClient(domain);
 
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
+          await OpenFeature.setProviderAndWait(domain, provider);
+          client.addHandler(ProviderEvents.Reconciling, handler);
+          client.addHandler(ProviderEvents.ContextChanged, handler);
+          await OpenFeature.setContext(domain, {});
 
-          client.addHandler(ProviderEvents.Stale, () => {
-            done();
-          });
+          expect(handler).toHaveBeenCalledTimes(2);
+        });
+      });
+
+      describe('provider has no context changed handler', () => {
+        it('only ContextChanged is emitted', async () => {
+          const provider = new MockProvider({ hasInitialize: false, hasContextChanged: false });
+
+          const handler = jest.fn(() => {});
+          const client = OpenFeature.getClient(domain);
+
+          await OpenFeature.setProviderAndWait(domain, provider);
+          client.addHandler(ProviderEvents.Reconciling, handler); // this should not be called
+          client.addHandler(ProviderEvents.ContextChanged, handler);
+          await OpenFeature.setContext(domain, {});
+
+          expect(handler).toHaveBeenCalledTimes(1);
         });
       });
     });
@@ -610,7 +642,7 @@ describe('Events', () => {
     describe('API', () => {
       describe('context set for same client', () => {
         it("If the provider's `on context changed` function terminates normally, associated `PROVIDER_CONTEXT_CHANGED` handlers MUST run.", (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
+          const provider = new MockProvider({ hasInitialize: false });
 
           OpenFeature.setProvider(domain, provider);
           OpenFeature.setContext(domain, {});
@@ -630,7 +662,7 @@ describe('Events', () => {
         });
 
         it("If the provider's `on context changed` function terminates abnormally, associated `PROVIDER_ERROR` handlers MUST run.", (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY, failOnContextChange: true });
+          const provider = new MockProvider({ hasInitialize: false, failOnContextChange: true });
 
           OpenFeature.setProvider(domain, provider);
           OpenFeature.setContext(domain, {});
@@ -652,7 +684,7 @@ describe('Events', () => {
 
       describe('context set for different client', () => {
         it("If the provider's `on context changed` function terminates normally, associated `PROVIDER_CONTEXT_CHANGED` handlers MUST run.", (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
+          const provider = new MockProvider({ hasInitialize: false });
           let runCount = 0;
 
           OpenFeature.setProvider(domain, provider);
@@ -683,7 +715,7 @@ describe('Events', () => {
         });
 
         it("If the provider's `on context changed` function terminates abnormally, associated `PROVIDER_ERROR` handlers MUST run.", (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY, failOnContextChange: true });
+          const provider = new MockProvider({ hasInitialize: false, failOnContextChange: true });
 
           OpenFeature.setProvider(domain, provider);
 
@@ -708,7 +740,7 @@ describe('Events', () => {
 
     describe('client', () => {
       it("If the provider's `on context changed` function terminates normally, associated `PROVIDER_CONTEXT_CHANGED` handlers MUST run.", (done) => {
-        const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
+        const provider = new MockProvider({ hasInitialize: false });
         const client = OpenFeature.getClient(domain);
 
         OpenFeature.setProvider(domain, provider);
@@ -729,7 +761,7 @@ describe('Events', () => {
       });
 
       it("If the provider's `on context changed` function terminates abnormally, associated `PROVIDER_ERROR` handlers MUST run.", (done) => {
-        const provider = new MockProvider({ initialStatus: ProviderStatus.READY, failOnContextChange: true });
+        const provider = new MockProvider({ hasInitialize: false, failOnContextChange: true });
         const client = OpenFeature.getClient(domain);
 
         OpenFeature.setProvider(domain, provider);
@@ -751,29 +783,47 @@ describe('Events', () => {
       });
     });
 
-    describe('provider', () => {
-      describe('has no onContextChange handler', () => {
-        it('runs API ContextChanged event handler', (done) => {
-          const noChangeHandlerProvider = 'noChangeHandlerProvider';
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY, noContextChanged: true });
+    describe('reentrant invocations', () => {
+      it('only fire one terminal event', async () => {
+        // provider context change will take 100ms
+        const provider = new MockProvider({ hasInitialize: false, asyncDelay: 100 });
+        const client = OpenFeature.getClient(domain);
+        let runs = 0;
 
-          OpenFeature.setProvider(noChangeHandlerProvider, provider);
-          OpenFeature.setContext(noChangeHandlerProvider, {});
+        OpenFeature.setProvider(domain, provider);
 
-          const handler = (details?: EventDetails) => {
-            try {
-              expect(details?.clientName).toEqual(noChangeHandlerProvider);
-              expect(details?.domain).toEqual(noChangeHandlerProvider);
-              expect(details?.providerName).toEqual(provider.metadata.name);
-              done();
-            } catch (e) {
-              done(e);
-            }
-          };
+        const handler = () => {
+          runs++;
+        };
 
-          OpenFeature.addHandler(ProviderEvents.ContextChanged, handler);
-        });
+        client.addHandler(ProviderEvents.ContextChanged, handler);
+        
+        // update context change twice
+        await Promise.all([
+          OpenFeature.setContext(domain, {}),
+          OpenFeature.setContext(domain, {}),
+        ]);
+
+        // should only have run once
+        expect(runs).toEqual(1);
       });
     });
   });
+
+  describe('Requirement 5.3.5', () => {
+    it('provider events update status', async () => {
+      // provider context change will take 100ms
+      const provider = new MockProvider({ hasInitialize: false });
+      OpenFeature.setProviderAndWait(domain, provider);
+      const client = OpenFeature.getClient(domain);
+      provider.events?.emit(ProviderEvents.Stale);
+      expect(client.providerStatus).toEqual(ProviderStatus.STALE);
+
+      provider.events?.emit(ProviderEvents.Ready);
+      expect(client.providerStatus).toEqual(ProviderStatus.READY);
+
+      provider.events?.emit(ProviderEvents.Error);
+      expect(client.providerStatus).toEqual(ProviderStatus.ERROR);
+    });
+  });
 });
diff --git a/packages/client/test/in-memory-provider.spec.ts b/packages/client/test/in-memory-provider.spec.ts
index b17922f3..5296bbff 100644
--- a/packages/client/test/in-memory-provider.spec.ts
+++ b/packages/client/test/in-memory-provider.spec.ts
@@ -1,10 +1,10 @@
-import { FlagNotFoundError, GeneralError, InMemoryProvider, ProviderEvents, ProviderStatus, StandardResolutionReasons, TypeMismatchError } from '../src';
+import { FlagNotFoundError, GeneralError, InMemoryProvider, ProviderEvents, StandardResolutionReasons, TypeMismatchError } from '../src';
 import { FlagConfiguration } from '../src/provider/in-memory-provider/flag-configuration';
 import { VariantNotFoundError } from '../src/provider/in-memory-provider/variant-not-found-error';
 
 describe('in-memory provider', () => {
   describe('initialize', () => {
-    it('Should have provider status as NOT_READY after instantiation and emit READY and have READY state after initialuzation', async () => {
+    it('Should not throw for valid flags', async () => {
       const booleanFlagSpec = {
         'a-boolean-flag': {
           variants: {
@@ -16,13 +16,10 @@ describe('in-memory provider', () => {
         },
       };
       const provider = new InMemoryProvider(booleanFlagSpec);
-      expect(provider.status).toBe(ProviderStatus.NOT_READY);
-
       await provider.initialize();
-      expect(provider.status).toBe(ProviderStatus.READY);
     });
 
-    it('Should have provider status as ERROR after instantiation, emit ERROR and have ERROR state if initialization throws', async () => {
+    it('Should throw on invalid flags', async () => {
       const throwingFlagSpec: FlagConfiguration = {
         'a-boolean-flag': {
           variants: {
@@ -37,12 +34,8 @@ describe('in-memory provider', () => {
         },
       };
       const provider = new InMemoryProvider(throwingFlagSpec);
-
-      expect(provider.status).toBe(ProviderStatus.NOT_READY);
       const someContext = {};
-
       await expect(provider.initialize(someContext)).rejects.toThrow();
-      expect(provider.status).toBe(ProviderStatus.ERROR);
     });
   });
 
@@ -511,9 +504,6 @@ describe('in-memory provider', () => {
       const configChangedSpy = jest.fn();
       provider.events.addHandler(ProviderEvents.ConfigurationChanged, configChangedSpy);
 
-      const readySpy = jest.fn();
-      provider.events.addHandler(ProviderEvents.Ready, readySpy);
-
       const newFlagSpec = {
         'some-flag': {
           variants: {
@@ -526,8 +516,6 @@ describe('in-memory provider', () => {
       await provider.putConfiguration(newFlagSpec);
 
       expect(configChangedSpy).toHaveBeenCalledWith({ flagsChanged: ['some-flag'] });
-      expect(readySpy).toHaveBeenCalled();
-      expect(provider.status).toBe(ProviderStatus.READY);
     });
   });
 
diff --git a/packages/client/test/open-feature.spec.ts b/packages/client/test/open-feature.spec.ts
index e061acea..d8e4f837 100644
--- a/packages/client/test/open-feature.spec.ts
+++ b/packages/client/test/open-feature.spec.ts
@@ -56,13 +56,6 @@ describe('OpenFeature', () => {
         expect(OpenFeature.providerMetadata.name).toBe('mock-events-success');
         expect(provider.initialize).toHaveBeenCalled();
       });
-
-      it('should not invoke initialize function if the provider is not in state NOT_READY', () => {
-        const provider = mockProvider({ initialStatus: ProviderStatus.READY });
-        OpenFeature.setProvider(provider);
-        expect(OpenFeature.providerMetadata.name).toBe('mock-events-success');
-        expect(provider.initialize).not.toHaveBeenCalled();
-      });
     });
 
     describe('Requirement 1.1.2.3', () => {
@@ -123,13 +116,13 @@ describe('OpenFeature', () => {
     it('should not close provider if it is used by another client', async () => {
       const provider1 = { ...mockProvider(), onClose: jest.fn() };
 
-      OpenFeature.setProvider('domain1', provider1);
-      OpenFeature.setProvider('domain2', provider1);
+      await OpenFeature.setProviderAndWait('domain1', provider1);
+      await OpenFeature.setProviderAndWait('domain2', provider1);
 
-      OpenFeature.setProvider('domain1', { ...provider1 });
+      await OpenFeature.setProviderAndWait('domain1', { ...provider1 });
       expect(provider1.onClose).not.toHaveBeenCalled();
 
-      OpenFeature.setProvider('domain2', { ...provider1 });
+      await OpenFeature.setProviderAndWait('domain2', { ...provider1 });
       expect(provider1.onClose).toHaveBeenCalledTimes(1);
     });
 
diff --git a/packages/server/e2e/step-definitions/evaluation.spec.ts b/packages/server/e2e/step-definitions/evaluation.spec.ts
index 33e349a2..f27436c0 100644
--- a/packages/server/e2e/step-definitions/evaluation.spec.ts
+++ b/packages/server/e2e/step-definitions/evaluation.spec.ts
@@ -7,7 +7,7 @@ import {
   StandardResolutionReasons,
 } from '@openfeature/core';
 import { defineFeature, loadFeature } from 'jest-cucumber';
-import { InMemoryProvider, OpenFeature, ProviderEvents } from '../../src';
+import { InMemoryProvider, OpenFeature } from '../../src';
 import flagConfiguration from './flags-config';
 
 // load the feature file.
@@ -19,17 +19,12 @@ const client = OpenFeature.getClient();
 const givenAnOpenfeatureClientIsRegisteredWithCacheDisabled = (
   given: (stepMatcher: string, stepDefinitionCallback: () => void) => void,
 ) => {
-  OpenFeature.setProvider(
-    new InMemoryProvider(flagConfiguration),
-  );
   given('a provider is registered with cache disabled', () => undefined);
 };
 
 defineFeature(feature, (test) => {
-  beforeAll((done) => {
-    client.addHandler(ProviderEvents.Ready, async () => {
-      done();
-    });
+  beforeAll(async () => {
+    await OpenFeature.setProvider(new InMemoryProvider(flagConfiguration));
   });
 
   afterAll(async () => {
diff --git a/packages/server/test/client.spec.ts b/packages/server/test/client.spec.ts
index 218d2aeb..ff84b753 100644
--- a/packages/server/test/client.spec.ts
+++ b/packages/server/test/client.spec.ts
@@ -1,3 +1,4 @@
+import { GeneralError } from '@openfeature/core';
 import {
   Client,
   ErrorCode,
@@ -10,6 +11,7 @@ import {
   OpenFeature,
   OpenFeatureClient,
   Provider,
+  ProviderFatalError,
   ProviderStatus,
   ResolutionDetails,
   StandardResolutionReasons,
@@ -545,6 +547,83 @@ describe('OpenFeatureClient', () => {
     });
   });
 
+  describe('Requirement 1.7.1, 1.7.3', () => {
+    const initProvider = {
+      metadata: {
+        name: 'initProvider',
+      },
+      initialize: () => {
+        return Promise.resolve();
+      }
+    } as unknown as Provider;
+    it('status must be READY if init resolves', async () => {
+      await OpenFeature.setProviderAndWait('1.7.1, 1.7.3', initProvider);
+      const client = OpenFeature.getClient('1.7.1, 1.7.3');
+      expect(client.providerStatus).toEqual(ProviderStatus.READY);
+    });
+  });
+
+  describe('Requirement 1.7.4', () => {
+    const errorProvider = {
+      metadata: {
+        name: 'errorProvider',
+      },
+      initialize: async () => {
+        return Promise.reject(new GeneralError());
+      }
+    } as unknown as Provider;
+    it('status must be ERROR if init rejects', async () => {
+      await expect(OpenFeature.setProviderAndWait('1.7.4', errorProvider)).rejects.toThrow();
+      const client = OpenFeature.getClient('1.7.4');
+      expect(client.providerStatus).toEqual(ProviderStatus.ERROR);
+    });
+  });
+
+  describe('Requirement 1.7.5, 1.7.6, 1.7.8', () => {
+    const fatalProvider = {
+      metadata: {
+        name: 'fatalProvider',
+      },
+      initialize: () => {
+        return Promise.reject(new ProviderFatalError());
+      }
+    } as unknown as Provider;
+    it('must short circuit and return PROVIDER_FATAL code if provider FATAL', async () => {
+      await expect(OpenFeature.setProviderAndWait('1.7.5, 1.7.6, 1.7.8', fatalProvider)).rejects.toThrow();
+      const client = OpenFeature.getClient('1.7.5, 1.7.6, 1.7.8');
+      expect(client.providerStatus).toEqual(ProviderStatus.FATAL);
+
+      const defaultVal = 'default';
+      const details = await client.getStringDetails('some-flag', defaultVal);
+      expect(details.value).toEqual(defaultVal);
+      expect(details.errorCode).toEqual(ErrorCode.PROVIDER_FATAL);
+    });
+  });
+
+  describe('Requirement 1.7.7', () => {
+    const neverReadyProvider = {
+      metadata: {
+        name: 'fatalProvider',
+      },
+      initialize: () => {
+        return new Promise(() => {
+          return; // promise never resolves
+        });
+      }
+    } as unknown as Provider;
+    it('must short circuit and return PROVIDER_NOT_READY code if provider NOT_READY', async () => {
+      OpenFeature.setProviderAndWait('1.7.7', neverReadyProvider).catch(() => {
+        // do nothing
+      });
+      const defaultVal = 'default';
+      const client = OpenFeature.getClient('1.7.7');
+      expect(client.providerStatus).toEqual(ProviderStatus.NOT_READY);
+      const details = await client.getStringDetails('some-flag', defaultVal);
+      expect(details.value).toEqual(defaultVal);
+      expect(details.errorCode).toEqual(ErrorCode.PROVIDER_NOT_READY);
+    });
+  });
+
   describe('Evaluation Context', () => {
     const provider = {
       metadata: {
diff --git a/packages/server/test/events.spec.ts b/packages/server/test/events.spec.ts
index 7f8033b7..1569b2b7 100644
--- a/packages/server/test/events.spec.ts
+++ b/packages/server/test/events.spec.ts
@@ -22,11 +22,10 @@ class MockProvider implements Provider {
   private failOnInit: boolean;
   private initDelay?: number;
   private enableEvents: boolean;
-  status?: ProviderStatus = undefined;
+  initialize?: () => Promise<void>;
 
   constructor(options?: {
     hasInitialize?: boolean;
-    initialStatus?: ProviderStatus;
     initDelay?: number;
     enableEvents?: boolean;
     failOnInit?: boolean;
@@ -34,7 +33,6 @@ class MockProvider implements Provider {
   }) {
     this.metadata = { name: options?.name ?? 'mock-provider' };
     this.hasInitialize = options?.hasInitialize ?? true;
-    this.status = options?.initialStatus ?? ProviderStatus.NOT_READY;
     this.initDelay = options?.initDelay ?? 0;
     this.enableEvents = options?.enableEvents ?? true;
     this.failOnInit = options?.failOnInit ?? false;
@@ -49,14 +47,10 @@ class MockProvider implements Provider {
         if (this.failOnInit) {
           throw new Error('Provider initialization failed');
         }
-
-        this.status = ProviderStatus.READY;
       });
     }
   }
 
-  initialize: jest.Mock<Promise<void>, []> | undefined;
-
   // eslint-disable-next-line @typescript-eslint/no-empty-function
   async onClose(): Promise<void> {}
 
@@ -84,6 +78,7 @@ describe('Events', () => {
 
   afterEach(async () => {
     await OpenFeature.clearProviders();
+    OpenFeature.clearHandlers();
     jest.clearAllMocks();
     domain = uuid();
     // hacky, but it's helpful to clear the handlers between tests
@@ -215,7 +210,6 @@ describe('Events', () => {
     it('anonymous provider with anonymous client should run non-init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
 
@@ -235,7 +229,6 @@ describe('Events', () => {
     it('anonymous provider with anonymous client should run init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
 
@@ -252,7 +245,6 @@ describe('Events', () => {
     it('anonymous provider with named client should run non-init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
       const unboundName = 'some-new-unbound-name';
@@ -273,7 +265,6 @@ describe('Events', () => {
     it('anonymous provider with named client should run init events', (done) => {
       const defaultProvider = new MockProvider({
         failOnInit: false,
-        initialStatus: ProviderStatus.NOT_READY,
         name: 'default',
       });
       const unboundName = 'some-other-unbound-name';
@@ -314,7 +305,7 @@ describe('Events', () => {
     it('handler added while while provider initializing runs', (done) => {
       const provider = new MockProvider({
         name: 'race',
-        initialStatus: ProviderStatus.NOT_READY,
+        hasInitialize: true,
         initDelay: TIMEOUT / 2,
       });
 
@@ -506,40 +497,27 @@ describe('Events', () => {
       provider.events?.emit(ProviderEvents.ConfigurationChanged);
     });
   });
-
+  
   describe('Requirement 5.3.3', () => {
     describe('API', () => {
       describe('Handlers attached after the provider is already in the associated state, MUST run immediately.', () => {
         it('Ready', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          OpenFeature.addHandler(ProviderEvents.Ready, () => {
-            done();
+          const provider = new MockProvider({ hasInitialize: false });
+  
+          OpenFeature.setProviderAndWait(domain, provider).then(() => {
+            OpenFeature.addHandler(ProviderEvents.Ready, () => {
+              done();
+            });
           });
         });
 
         it('Error', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.ERROR });
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          OpenFeature.addHandler(ProviderEvents.Error, () => {
-            done();
-          });
-        });
-
-        it('Stale', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.STALE });
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          OpenFeature.addHandler(ProviderEvents.Stale, () => {
-            done();
+          const provider = new MockProvider({ failOnInit: true });
+  
+          OpenFeature.setProviderAndWait(domain, provider).catch(() => {
+            OpenFeature.addHandler(ProviderEvents.Error, () => {
+              done();
+            });
           });
         });
       });
@@ -548,41 +526,44 @@ describe('Events', () => {
     describe('client', () => {
       describe('Handlers attached after the provider is already in the associated state, MUST run immediately.', () => {
         it('Ready', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.READY });
+          const provider = new MockProvider({ hasInitialize: false });
           const client = OpenFeature.getClient(domain);
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          client.addHandler(ProviderEvents.Ready, () => {
-            done();
+  
+          OpenFeature.setProviderAndWait(domain, provider).then(() => {
+            client.addHandler(ProviderEvents.Ready, () => {
+              done();
+            });
           });
         });
 
         it('Error', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.ERROR });
+          const provider = new MockProvider({ failOnInit: true });
           const client = OpenFeature.getClient(domain);
-
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
-
-          client.addHandler(ProviderEvents.Error, () => {
-            done();
+  
+          OpenFeature.setProviderAndWait(domain, provider).catch(() => {
+            client.addHandler(ProviderEvents.Error, () => {
+              done();
+            });
           });
         });
+      });
+    });
+  });
 
-        it('Stale', (done) => {
-          const provider = new MockProvider({ initialStatus: ProviderStatus.STALE });
-          const client = OpenFeature.getClient(domain);
+  describe('Requirement 5.3.5', () => {
+    it('provider events update status', async () => {
+      // provider context change will take 100ms
+      const provider = new MockProvider({ hasInitialize: false });
+      OpenFeature.setProviderAndWait(domain, provider);
+      const client = OpenFeature.getClient(domain);
+      provider.events?.emit(ProviderEvents.Stale);
+      expect(client.providerStatus).toEqual(ProviderStatus.STALE);
 
-          OpenFeature.setProvider(domain, provider);
-          expect(provider.initialize).not.toHaveBeenCalled();
+      provider.events?.emit(ProviderEvents.Ready);
+      expect(client.providerStatus).toEqual(ProviderStatus.READY);
 
-          client.addHandler(ProviderEvents.Stale, () => {
-            done();
-          });
-        });
-      });
+      provider.events?.emit(ProviderEvents.Error);
+      expect(client.providerStatus).toEqual(ProviderStatus.ERROR);
     });
   });
 });
diff --git a/packages/server/test/open-feature.spec.ts b/packages/server/test/open-feature.spec.ts
index a1a0bb66..803ed14f 100644
--- a/packages/server/test/open-feature.spec.ts
+++ b/packages/server/test/open-feature.spec.ts
@@ -7,7 +7,6 @@ const mockProvider = (config?: { initialStatus?: ProviderStatus; runsOn?: Paradi
       name: 'mock-events-success',
     },
     runsOn: config?.runsOn || 'server',
-    status: config?.initialStatus || ProviderStatus.NOT_READY,
     initialize: jest.fn(() => {
       return Promise.resolve('started');
     }),
@@ -56,13 +55,6 @@ describe('OpenFeature', () => {
         expect(OpenFeature.providerMetadata.name).toBe('mock-events-success');
         expect(provider.initialize).toHaveBeenCalled();
       });
-
-      it('should not invoke initialize function if the provider is not in state NOT_READY', () => {
-        const provider = mockProvider({ initialStatus: ProviderStatus.READY });
-        OpenFeature.setProvider(provider);
-        expect(OpenFeature.providerMetadata.name).toBe('mock-events-success');
-        expect(provider.initialize).not.toHaveBeenCalled();
-      });
     });
 
     describe('Requirement 1.1.2.3', () => {
@@ -123,13 +115,13 @@ describe('OpenFeature', () => {
     it('should not close provider if it is used by another client', async () => {
       const provider1 = { ...mockProvider(), onClose: jest.fn() };
 
-      OpenFeature.setProvider('domain1', provider1);
-      OpenFeature.setProvider('domain2', provider1);
+      await OpenFeature.setProviderAndWait('domain1', provider1);
+      await OpenFeature.setProviderAndWait('domain2', provider1);
 
-      OpenFeature.setProvider('domain1', { ...provider1 });
+      await OpenFeature.setProviderAndWait('domain1', { ...provider1 });
       expect(provider1.onClose).not.toHaveBeenCalled();
 
-      OpenFeature.setProvider('domain2', { ...provider1 });
+      await OpenFeature.setProviderAndWait('domain2', { ...provider1 });
       expect(provider1.onClose).toHaveBeenCalledTimes(1);
     });