Skip to content

Commit 353a4e3

Browse files
nicklaslclaude
andauthored
feat: add assignment_origin to ResolvedFlag (#472)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e4d7baa commit 353a4e3

17 files changed

Lines changed: 114 additions & 30 deletions

File tree

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/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/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
]
Binary file not shown.

openfeature-provider/go/confidence/internal/proto/resolver/api.pb.go

Lines changed: 17 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openfeature-provider/js/src/ConfidenceServerProviderLocal.e2e.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ describe.each([
7777
flagMetadata: {},
7878
value: 3.6,
7979
shouldApply: true,
80+
assignmentOrigin: 'flags/web-sdk-e2e-flag/rules/zphggscnfdpvcp4jy5ui',
8081
};
8182

8283
expect(await client.getNumberDetails('web-sdk-e2e-flag.obj.double', 1, ctx)).toEqual(expectedObject);

openfeature-provider/js/src/ConfidenceServerProviderLocal.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ describe('remote materialization for sticky assignments', () => {
277277
value: { enabled: true },
278278
reason: RESOLVE_REASON_MATCH,
279279
shouldApply: true,
280+
assignmentOrigin: 'flags/test-flag/rules/rule1',
280281
},
281282
],
282283
resolveToken: new Uint8Array(),
@@ -317,6 +318,7 @@ describe('remote materialization for sticky assignments', () => {
317318
value: { enabled: true },
318319
reason: RESOLVE_REASON_MATCH,
319320
shouldApply: true,
321+
assignmentOrigin: 'flags/test-flag/rules/rule1',
320322
},
321323
],
322324
resolveToken: new Uint8Array(),
@@ -362,6 +364,7 @@ describe('remote materialization for sticky assignments', () => {
362364
value: { color: 'blue', size: 10 },
363365
reason: ResolveReason.RESOLVE_REASON_MATCH,
364366
shouldApply: true,
367+
assignmentOrigin: 'flags/my-flag/rules/rule1',
365368
},
366369
],
367370
resolveToken: new Uint8Array(),
@@ -405,6 +408,7 @@ describe('remote materialization for sticky assignments', () => {
405408
value: { ok: true },
406409
reason: ResolveReason.RESOLVE_REASON_MATCH,
407410
shouldApply: true,
411+
assignmentOrigin: 'flags/test-flag/rules/rule1',
408412
},
409413
],
410414
resolveToken: new Uint8Array(),
@@ -443,6 +447,7 @@ describe('remote materialization for sticky assignments', () => {
443447
value: { ok: true },
444448
reason: ResolveReason.RESOLVE_REASON_MATCH,
445449
shouldApply: true,
450+
assignmentOrigin: 'flags/test-flag/rules/rule1',
446451
},
447452
],
448453
resolveToken: new Uint8Array(),
@@ -479,6 +484,7 @@ describe('SDK telemetry', () => {
479484
value: { enabled: true },
480485
reason: RESOLVE_REASON_MATCH,
481486
shouldApply: true,
487+
assignmentOrigin: 'flags/test-flag/rules/rule1',
482488
},
483489
],
484490
resolveToken: new Uint8Array(),
@@ -522,6 +528,7 @@ describe('registerResolve telemetry', () => {
522528
value: { enabled: true },
523529
reason: RESOLVE_REASON_MATCH,
524530
shouldApply: true,
531+
assignmentOrigin: 'flags/test-flag/rules/rule1',
525532
},
526533
],
527534
resolveToken: new Uint8Array(),
@@ -581,6 +588,7 @@ describe('registerResolve telemetry', () => {
581588
value: { enabled: 'not-a-boolean' },
582589
reason: RESOLVE_REASON_MATCH,
583590
shouldApply: true,
591+
assignmentOrigin: 'flags/test-flag/rules/rule1',
584592
},
585593
],
586594
resolveToken: new Uint8Array(),

openfeature-provider/js/src/flag-bundle.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('FlagBundle', () => {
2525
value: { enabled: true },
2626
reason: ResolveReason.RESOLVE_REASON_MATCH,
2727
shouldApply: true,
28+
assignmentOrigin: '',
2829
},
2930
],
3031
});
@@ -99,6 +100,7 @@ describe('FlagBundle', () => {
99100
value: { key: 'value' },
100101
reason: ResolveReason.RESOLVE_REASON_MATCH,
101102
shouldApply: true,
103+
assignmentOrigin: '',
102104
},
103105
],
104106
});
@@ -118,6 +120,7 @@ describe('FlagBundle', () => {
118120
value: undefined,
119121
reason: ResolveReason.RESOLVE_REASON_NO_SEGMENT_MATCH,
120122
shouldApply: false,
123+
assignmentOrigin: '',
121124
},
122125
],
123126
});
@@ -136,13 +139,15 @@ describe('FlagBundle', () => {
136139
value: { x: 1 },
137140
reason: ResolveReason.RESOLVE_REASON_MATCH,
138141
shouldApply: true,
142+
assignmentOrigin: '',
139143
},
140144
{
141145
flag: 'flags/apply-false',
142146
variant: 'v1',
143147
value: { x: 2 },
144148
reason: ResolveReason.RESOLVE_REASON_MATCH,
145149
shouldApply: false,
150+
assignmentOrigin: '',
146151
},
147152
],
148153
});
@@ -200,6 +205,7 @@ describe('FlagBundle', () => {
200205
value: { config: { enabled: true, limit: 10 } },
201206
reason: ResolveReason.RESOLVE_REASON_MATCH,
202207
shouldApply: true,
208+
assignmentOrigin: '',
203209
},
204210
],
205211
});

openfeature-provider/js/src/flag-bundle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ export const decodeToken: (base64: string) => Uint8Array = bytesFromBase64;
2323

2424
export function create({ resolveId, resolveToken, resolvedFlags }: ResolveFlagsResponse): FlagBundle {
2525
const flags = Object.fromEntries(
26-
resolvedFlags.map(({ flag, reason, variant, value, shouldApply }) => {
26+
resolvedFlags.map(({ flag, reason, variant, value, shouldApply, assignmentOrigin }) => {
2727
const name = flag.slice(FLAG_PREFIX.length);
2828
const details: ResolutionDetails<FlagObject | null> = {
2929
reason: convertReason(reason),
3030
variant,
3131
value: value ?? null,
3232
shouldApply,
33+
assignmentOrigin,
3334
};
3435
return [name, details];
3536
}),

0 commit comments

Comments
 (0)