From 068a9a5dab0e8ff3addbef38b01bdda91fbc52bf Mon Sep 17 00:00:00 2001
From: Thomas Poignant <thomas.poignant@gofeatureflag.org>
Date: Wed, 27 Nov 2024 22:54:59 +0100
Subject: [PATCH] doc(specification): Flag set specification proposal

Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
---
 .../pages/specification/20241027-flagsets.md  | 200 ++++++++++++++++++
 1 file changed, 200 insertions(+)
 create mode 100644 website/src/pages/specification/20241027-flagsets.md

diff --git a/website/src/pages/specification/20241027-flagsets.md b/website/src/pages/specification/20241027-flagsets.md
new file mode 100644
index 00000000000..6809ca3794c
--- /dev/null
+++ b/website/src/pages/specification/20241027-flagsets.md
@@ -0,0 +1,200 @@
+---
+title: Flag sets
+Description: Flag sets are a way to group flags together.
+---
+
+# Flag sets
+
+|                      |                                                   |
+|----------------------|---------------------------------------------------|
+| **Creation Date**    | 27/10/2024                                        |
+| **Last Update Date** | 27/10/2024                                        |
+| **Authors**          | Thomas Poignant                                   |
+| **Status**           | ![draft](https://img.shields.io/badge/-draft-red) |
+
+## Definition
+
+> A collection of related flags. This grouping helps organize feature flags based on their intended use, facilitating easier management and deployment.
+
+_Source: [Openfeature glossary](https://openfeature.dev/specification/glossary/#flag-set)._
+
+
+## Context
+
+GO Feature Flag is supporting both flag evaluation for client and server-side.  
+While the server side evaluations are evaluating flags 1 by 1, the client-side providers are evaluating flags in bulk.
+The main reason why client are evaluating flags in bulk is because in the client-side the evaluation context is not changing
+for every evaluation, so in order to limit the number of requests to the relay proxy, we are evaluating flags in bulk and keeping
+them in memory inside the different client providers _(`web`, `iOS` and `android`)_ for OpenFeature.
+
+### Sequence diagram of a client-side evaluation
+
+```mermaid
+sequenceDiagram
+    participant app as Application
+    participant Client as Openfeature SDK
+    participant Provider as GO Feature Flag Provider
+    participant RelayProxy
+    app->>Client: SetContext(myEvaluationCtx)
+    Client->>Provider: init()
+    Provider->>RelayProxy: call /ofrep/v1/evaluate/flags
+    RelayProxy-->>Provider: return all evaluation responses
+    Provider->>Provider: Cache evaluation responses
+    Provider-->>Client: 
+    Client -->> app: 
+
+    app->>Client: getBooleanValue(...)
+    Client->>Provider: resolveBooleanValue(...)
+    Provider-->>Client: evaluation response from cache for the flag
+    Client -->> app: flag value
+```
+:::note
+For simplicity, we are not showing the cache mechanism in the sequence diagram, and how the cache is updated in case of flag configuration changes.
+:::
+
+### Why introducing flag sets?
+
+As of today, in the client-side paradigm, we are evaluating all the flags available in GO Feature Flag based on the received evaluation context.
+This means that we are evaluating all the flags available in the project, but in some cases, we might want to evaluate only a subset of the flags.
+
+**When do we want to use flag sets?**
+- We have multiple teams using the same GO Feature Flag instance, and we want to separate the flags evaluated by each team.
+- We have different platforms using GO Feature Flag, and we want to limit which flags are evaluated by each platform.
+- We want to give access to a list of flags to a specific user group.
+- We want to allow 2 flags with the same name if they are used by different teams or platforms.
+
+**For all those points, as of today the only way to achieve this is to run multiple instances of GO Feature Flag, which is not ideal.**
+
+## Requirements
+
+- A flag can be part of only 1 flag set.
+- Flag name are unique within a flag set, but not across flag sets.
+- GO Feature Flag should have a `default` flag set for users not using the flag sets feature _(the behaviours should be exactly the same as of today if the feature is not used)_.
+- When calling the bulk evaluation APIs (`/ofrep/v1/evaluate/flags` or `/v1/allflags`), we will determine which flag set to evaluate based on API key used.
+- The bulk evaluation APIs should evaluate only 1 flag set at a time _(to avoid collision in flag names)_.
+- It should be able to specify which flag set to evaluate when calling the evaluation APIs. _(e.g. `/ofrep/v1/evaluate/flags` or `/ofrep/v1/evaluate/flags/{flag_key}`)_.
+  - Ideally we should be able to know which flag set to used based on the API Key used.
+  - If GOFF is configured to be public, we should be able to specify the flag set to evaluate in the request _(with a specific header ex:`goff-flag-set`)_.
+  - In the providers, we should be able to specify the flag set to evaluate directly in the constructor _(by providing an API Key, or the name of the flag set)_.
+- Admin API Keys, should be able to evaluate all the flag sets _(to be able to see all the flags available in the project)_. If none specified in the request, the default flag set should be evaluated.
+
+
+## Out of scope for now
+- Dynamic flag sets based on the evaluation context _([as mentioned in this slack message](https://gophers.slack.com/archives/C029TH8KDFG/p1732703075509229))_.
+- Single retrievers for multiple flag sets _(as proposed in https://github.com/thomaspoignant/go-feature-flag/issues/2314)_.
+
+_Even if out of scope for now, those are interesting options that we may want to implement later._
+
+## Proposed solution
+
+### Solution 1: 1 flag set per file
+
+In this solution we consider that we need at least one file per flag set.  
+All the flags retrieved by a `retriever` _(aka in the same file)_ will be associated to the same flag set.
+
+We can specify the flag set name in the configuration file.  
+As of today, we can still have multiple files for 1 flag set _(by specifying the same `flagSet` name in each file)_,
+and we will have the same mechanism as of today _(with flag overridden in case of flag name collision)_.
+
+#### Flags configuration file
+```yaml
+# config-file1.goff.yaml
+# Syntax used in this example is just for the sake of the example, it is not the final syntax.
+flagSet: flagset-teamA
+
+featureA-enabled:
+  variations:
+      enabled: true
+      disabled: false
+  defaultRule:
+    variation: enabled
+```
+```yaml
+# config-file2.goff.yaml
+flagSet: flagset-teamB
+
+featureA-enabled:
+  variations:
+      enabled: true
+      disabled: false
+  defaultRule:
+    variation: enabled
+```
+
+#### Relay-proxy configuration example
+```yaml
+# ...
+retrievers:
+  - kind: file
+    path: config-file1.goff.yaml
+  - kind: file
+    path: config-file2.goff.yaml
+
+authorizedKeys:
+  evaluation:
+    - apikey1 # owner: userID1
+    - apikey2 # owner: userID2
+  admin:
+    - apikey3
+```
+
+**PRO**
+- It is simple to configure a flag set by putting all the flags at the same place.
+- It is easy to understand which flags are part of a flag set.
+- It is easy to give ownership of a flag set to a team, by giving them access to the file.
+
+**CON**
+- If we want the same flag to be part of multiple flag sets, we need to duplicate the flag in multiple files.
+
+### Solution 2: specify the flag set in the retriever configuration
+
+In this solution we consider that we need at least one file per flag set.  
+All the flags retrieved by a `retriever` _(aka in the same file)_ will be associated to the same flag set.
+
+We associate the flag set to the retriever in the configuration,
+#### Flags configuration file
+```yaml
+# config-file1.goff.yaml
+featureA-enabled:
+  variations:
+      enabled: true
+      disabled: false
+  defaultRule:
+    variation: enabled
+```
+```yaml
+# config-file2.goff.yaml
+featureA-enabled:
+  variations:
+      enabled: true
+      disabled: false
+  defaultRule:
+    variation: enabled
+```
+
+#### Relay-proxy configuration example
+```yaml
+# ...
+retrievers:
+  - kind: file
+    path: config-file1.goff.yaml
+    flagSet: flagset-teamA
+  - kind: file
+    path: config-file2.goff.yaml
+    flagSet: flagset-teamB
+
+authorizedKeys:
+  evaluation:
+    - apikey1 # owner: userID1
+    - apikey2 # owner: userID2
+  admin:
+    - apikey3
+```
+### Solution 3
+:::note
+Feel free to propose other solutions here.
+:::
+
+## Decision
+
+## Consequences