Skip to content

Commit a2f4e12

Browse files
nicklaslclaude
andcommitted
feat: add provider_init_rate to telemetry
On the first telemetry flush after provider initialization, include a ProviderInitRate entry with labels describing the SDK configuration (e.g. encryption: true/false). This lets the backend track which customers have adopted encryption without tagging every metric. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c54e1da commit a2f4e12

71 files changed

Lines changed: 1352 additions & 375 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

confidence-cloudflare-resolver/CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ RUSTFLAGS='--cfg getrandom_backend="wasm_js"' cargo build --target wasm32-unknow
6262
## Compile-Time Data
6363

6464
Two files are embedded at compile time from `../../data/`:
65-
- `resolver_state_current.pb` — Protobuf-encoded `SetResolverStateRequest` containing resolver state + account ID
65+
- `resolver_state_current.pb` — Protobuf-encoded `ClientResolverState` containing resolver state + account ID
6666
- `encryption_key` — Base64-encoded encryption key for resolve tokens
6767

6868
## Deployer

confidence-cloudflare-resolver/src/lib.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@ use confidence_resolver::proto::confidence::flags::resolver::v1::{
2727
use confidence_resolver::Client;
2828
use once_cell::sync::Lazy;
2929

30-
/// SetResolverStateRequest message from the CDN.
31-
/// This matches the protobuf message format returned by the CDN.
3230
#[derive(Clone, PartialEq, Message)]
33-
pub struct SetResolverStateRequest {
31+
pub struct ClientResolverState {
3432
#[prost(bytes = "bytes", tag = "1")]
3533
pub state: Bytes,
3634
#[prost(string, tag = "2")]
37-
pub account_id: String,
35+
pub account: String,
3836
}
3937

4038
/// The CDN response containing both the state and account_id
@@ -58,16 +56,16 @@ static FLAGS_LOGS_QUEUE: OnceLock<Queue> = OnceLock::new();
5856
static CONFIDENCE_CLIENT_SECRET: OnceLock<String> = OnceLock::new();
5957

6058
/// Parsed CDN state request containing both state and account_id
61-
static CDN_STATE_REQUEST: Lazy<SetResolverStateRequest> = Lazy::new(|| {
62-
SetResolverStateRequest::decode(Bytes::from_static(CDN_STATE_BYTES))
63-
.expect("Failed to decode SetResolverStateRequest from CDN state")
59+
static CDN_STATE_REQUEST: Lazy<ClientResolverState> = Lazy::new(|| {
60+
ClientResolverState::decode(Bytes::from_static(CDN_STATE_BYTES))
61+
.expect("Failed to decode ClientResolverState from CDN state")
6462
});
6563

6664
static RESOLVER_STATE: Lazy<ResolverState> = Lazy::new(|| {
6765
let cdn_request = &*CDN_STATE_REQUEST;
6866
ResolverState::from_proto(
6967
cdn_request.state.to_vec().try_into().unwrap(),
70-
&cdn_request.account_id,
68+
&cdn_request.account,
7169
None,
7270
)
7371
.unwrap()

confidence-resolver/protos/confidence/flags/resolver/v1/api.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,7 @@ message ResolvedFlag {
182182

183183
// Determines whether the flag should be applied in the clients
184184
bool should_apply = 6 [(google.api.field_behavior) = OUTPUT_ONLY];
185+
186+
// The origin of the assignment, e.g. the rule name.
187+
string assignment_origin = 7 [(google.api.field_behavior) = OUTPUT_ONLY];
185188
}

confidence-resolver/protos/confidence/flags/resolver/v1/internal_api.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ message WriteFlagAssignedResponse {
112112
// consumers everything needed to reconstruct bucket boundaries and resample
113113
// between different histogram configurations if needed.
114114
message TelemetryData {
115+
reserved 1; // was: int64 dropped_events
116+
115117
// Information about the SDK/provider
116118
Sdk sdk = 2 [
117119
(google.api.field_behavior) = OPTIONAL
@@ -128,6 +130,14 @@ message TelemetryData {
128130
// Set from the confidence-resolver crate version at build time.
129131
string resolver_version = 8;
130132

133+
repeated ProviderInitRate provider_init_rate = 9;
134+
135+
message ProviderInitRate {
136+
uint32 count = 1;
137+
reserved 2; // status — tbd
138+
map<string, string> labels = 3;
139+
}
140+
131141
message ResolveLatency {
132142
// Delta sum of observed values since the last flush.
133143
uint32 sum = 1;

confidence-resolver/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,7 @@ impl<'a> TryFrom<&ResolvedValue<'a>> for flags_resolver::ResolvedFlag {
17071707
flag: value.flag.name.clone(),
17081708
reason: value.inner.reason,
17091709
should_apply: value.should_apply(),
1710+
assignment_origin: value.inner.rule.clone(),
17101711
..Default::default()
17111712
};
17121713

confidence-resolver/src/resolver_spec_tests.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ struct SpecResolvedFlag {
132132
value: Option<serde_json::Value>,
133133
should_apply: Option<bool>,
134134
assignment_id: Option<String>,
135+
assignment_origin: Option<String>,
135136
}
136137

137138
// ---------------------------------------------------------------------------
@@ -461,6 +462,15 @@ fn run_spec_test(state: &ResolverState, test_case: &SpecTestCase) {
461462
);
462463
}
463464

465+
// assignmentOrigin assertion
466+
if let Some(expected_origin) = &expected_flag.assignment_origin {
467+
assert_eq!(
468+
&actual.assignment_origin, expected_origin,
469+
"[{}] assignmentOrigin mismatch for flag '{}'",
470+
test_case.name, expected_flag.flag,
471+
);
472+
}
473+
464474
// assignmentId assertion (from resolve token)
465475
if let Some(expected_assignment_id) = &expected_flag.assignment_id {
466476
let token = resolver

confidence-resolver/src/telemetry.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ impl Telemetry {
498498
state_age,
499499
memory_bytes: (self.memory_provider)(),
500500
resolver_version: crate::version::VERSION.to_string(),
501+
provider_init_rate: Vec::new(),
501502
}
502503
}
503504
}

confidence-resolver/test-payloads/resolver-spec/tests.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4114,5 +4114,28 @@
41144114
"expectError": null
41154115
}
41164116
}
4117+
},
4118+
{
4119+
"name": "assignment_origin_is_matched_rule_name",
4120+
"resolveRequest": {
4121+
"flags": ["flags/simple-flag"],
4122+
"evaluationContext": { "targeting_key": "user1", "country": "SE" },
4123+
"clientSecret": "test-secret"
4124+
},
4125+
"expectedResult": {
4126+
"general": {
4127+
"resolvedFlags": [
4128+
{
4129+
"flag": "flags/simple-flag",
4130+
"reason": "RESOLVE_REASON_MATCH",
4131+
"variant": "on",
4132+
"value": { "enabled": true },
4133+
"shouldApply": true,
4134+
"assignmentOrigin": "flags/simple-flag/rules/rule1"
4135+
}
4136+
],
4137+
"expectError": null
4138+
}
4139+
}
41174140
}
41184141
]

openfeature-provider/INTEGRATION_GUIDE.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ For language-specific installation and quick start instructions, see your provid
1313
## Table of Contents
1414

1515
1. [Getting Your Credentials](#getting-your-credentials)
16-
2. [Error Handling](#error-handling)
17-
3. [Sticky Assignments](#sticky-assignments)
18-
4. [Deferred Apply and Resolve Token Security](#deferred-apply-and-resolve-token-security)
16+
2. [Encryption](#encryption)
17+
3. [Error Handling](#error-handling)
18+
4. [Sticky Assignments](#sticky-assignments)
19+
5. [Deferred Apply and Resolve Token Security](#deferred-apply-and-resolve-token-security)
1920

2021
---
2122

@@ -29,6 +30,38 @@ Before integrating any Confidence provider, you'll need a **client secret** from
2930

3031
---
3132

33+
## Encryption
34+
35+
The flag state downloaded by local-resolve providers contains your flag rules and targeting segments. To protect this data at rest and in transit, Confidence supports **encrypting the flag state**. The state is decrypted only when it is loaded into the resolver component inside the provider — it is never stored or transmitted in plaintext.
36+
37+
### Getting Your Encryption Key
38+
39+
The encryption key is available in the [Confidence Admin view](https://app.confidence.spotify.com/admin/clients), next to the client credentials for your integration.
40+
41+
### Migration
42+
43+
Encryption support was introduced in the following provider versions:
44+
45+
| Provider | Version | Package |
46+
|----------|---------|---------|
47+
| JavaScript | `0.16.0` | `@spotify-confidence/openfeature-server-provider-local` |
48+
| Java | `0.16.0` | `com.spotify.confidence:openfeature-provider-local` |
49+
| Go | `0.19.0` | `github.com/spotify/confidence-resolver/openfeature-provider/go` |
50+
| Python | `0.9.0` | `confidence-openfeature-provider` |
51+
| Rust | `0.7.0` | `spotify-confidence-openfeature-provider-local` |
52+
53+
We strongly recommend enabling encryption now by passing the encryption key when creating your provider. See your provider's README for the exact configuration:
54+
55+
- [JavaScript](js/README.md#encryption)
56+
- [Java](java/README.md#encryption)
57+
- [Go](go/README.md#encryption)
58+
- [Python](python/README.md#encryption)
59+
- [Rust](rust/README.md#encryption)
60+
61+
> **⚠️ Upcoming change:** Encryption will be made **mandatory** in a future SDK release. We will communicate a timeline and migration path before legacy (unencrypted) provider versions are affected. Adopting encryption now ensures a smooth transition when that happens.
62+
63+
---
64+
3265
## Error Handling
3366

3467
All Confidence providers use a **default value fallback** pattern to ensure your application continues to function even when flag evaluation fails.

openfeature-provider/go/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,25 @@ You'll need a **client secret** from Confidence to use this provider.
3535
- Creating a test flag for verification
3636
- Best practices for credential storage
3737

38+
## Encryption
39+
40+
The provider supports encrypting the flag state to protect your flag rules and targeting segments at rest and in transit. The state is decrypted only when loaded into the resolver.
41+
42+
**📖 See the [Integration Guide: Encryption](../INTEGRATION_GUIDE.md#encryption)** for background and migration details.
43+
44+
Pass the encryption key in `ProviderConfig`:
45+
46+
```go
47+
provider, err := confidence.NewProvider(ctx, confidence.ProviderConfig{
48+
ClientSecret: "your-client-secret",
49+
EncryptionKey: "your-encryption-key", // Get from Confidence Admin view
50+
})
51+
```
52+
53+
The encryption key is available in the [Confidence Admin view](https://app.confidence.spotify.com/admin/clients), next to your client credentials.
54+
55+
> **⚠️ Upcoming change:** Encryption will be made **mandatory** in a future SDK release. We will communicate a timeline and migration path before legacy provider versions are affected. We strongly recommend enabling it now.
56+
3857
## Quick Start
3958

4059
```go
@@ -158,6 +177,7 @@ The `ProviderConfig` struct contains all configuration options for the provider:
158177
#### Required Fields
159178

160179
- `ClientSecret` (string): The client secret used for authentication and flag evaluation
180+
- `EncryptionKey` (string): Encryption key for decrypting the flag state. Found in the [Confidence Admin view](https://app.confidence.spotify.com/admin/clients). Will be required in a future release.
161181

162182
#### Optional Fields
163183

0 commit comments

Comments
 (0)