feat!: evaluate feature flags through the v4 endpoints - #53
Open
lourens-octopus wants to merge 10 commits into
Open
feat!: evaluate feature flags through the v4 endpoints#53lourens-octopus wants to merge 10 commits into
lourens-octopus wants to merge 10 commits into
Conversation
lourens-octopus
force-pushed
the
lourens/bmbb-753
branch
from
August 11, 2026 20:50
00cc80b to
891aee6
Compare
The switch to v4 needs the evaluator, which lives in the provider package, to call ServerSideEvaluation.evaluate(). Java package access is not hierarchical, so keeping the v4 types in a sub-package would mean making them public — permanently, since nothing later removes the split. With v4 becoming the only contract, the sub-package has served its purpose: it kept an unreleased contract out of the way while v3 was live. Everything stays package-private in one package, and the public API is unchanged. A pure move: no behaviour changes, and the two test-only shims that only existed to cross the package boundary are gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Switches the provider from the v3 toggle endpoints to the v4 feature-flag endpoints, and from v3's segment and rollout matching to the rules-based evaluation added earlier. - GET api/feature-flags/check/v4/ and api/feature-flags/evaluations/v4/ - EvaluationResponse replaces FeatureToggles - evaluate(slug, context) resolves a ServerSideEvaluation, so the value and reason come from the server or from the client-side rules it deferred, and a malformed response reports the problem it found - an unrecognised slug throws FlagNotFoundError with the message the other provider libraries use - specification fixtures move to the v4 evaluations set v3's types remain, unused, for BMBB-780. BREAKING CHANGE: the provider now calls the v4 feature-flag endpoints and evaluates rules-based flags. A server that only serves the v3 toggle endpoints is no longer supported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lourens-octopus
force-pushed
the
lourens/bmbb-753
branch
from
August 11, 2026 21:01
891aee6 to
27aea2c
Compare
A null array entry made findEvaluationBySlug throw NullPointerException, which is not an OpenFeatureError, so every flag in the response fell back to the caller's default with ErrorCode.GENERAL until the content hash changed. A null entry carries no slug and can never be the flag being asked for, so it is now skipped. A null response body was worse: it produced a context whose content hash looked valid, so the check endpoint reported no change and the provider never recovered. It is now treated as a failed fetch, like every other unusable response. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deserializing the array in one call aborted on the first wrongly-typed field anywhere in it, so "percentage": "lots" on one flag left every flag in the response falling back to the caller's default. Missing fields were already contained to the flag that carried them, because they are reported when that flag is evaluated; a field of the wrong type never gets that far, because it fails while the response is being read. An evaluation that cannot be read is left out and its slug logged, so it resolves as not found rather than being guessed at, and the rest of the response is unaffected. Note this is stricter than the .NET provider, which still deserializes the array in one call and so retains the original behaviour. Worth raising there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ordering the slug comparison so a null slug in the payload is tolerated moved the null-intolerance onto the caller's flag key, which is the one that actually arrives null — an unset configuration value, say. v3 answered FLAG_NOT_FOUND; a NullPointerException is not an OpenFeatureError, so the SDK reported ErrorCode.GENERAL with a raw exception message instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The ParseError messages are shared verbatim with the other provider libraries, so they name the problem but not the flag — v3's message did name it, and the tests deleted with the v3 path asserted exactly that. Rather than diverge from the shared messages, the slug is logged beside the problem, which is what makes a malformed response traceable when a server rollout affects several flags at once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The set of already-warned slugs lived on the evaluator, which is replaced whenever the response changes, so it forgot every slug about once per cache interval and warned repeatedly for a single typo — the opposite of what it was for. It now belongs to the cache, which outlives the evaluators it builds. It is also bounded. Slugs come from the caller, who may derive them (one per tenant, say), and an unbounded set would have held one string per distinct unknown slug for the life of the process. Keys are lowercased with Locale.ROOT rather than the default locale: under a Turkish locale "MY-FLAG-I" lowercases to a dotless "my-flag-ı", which would not match "my-flag-i" and so would warn twice for one slug. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reasons are no longer OpenFeature's Reason values, which v3 returned; a caller branching on Reason.TARGETING_MATCH or keying metrics on the reason sees free-text sentences instead, with a cardinality that grows with the number of rule names. Nothing about that surfaces as a compile error, so it belongs in the breaking-change note rather than only in the code. BREAKING CHANGE: evaluation reasons are no longer OpenFeature's Reason values. A server-resolved flag reports the service's own reason, a client-side match reports "Matched rule '<name>'." and a flag that matched no rule reports "Did not match any rules." Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An evaluation that could not be read was left out of the response, so the flag resolved as not found. Every other malformed shape in the shared specification resolves with PARSE_ERROR against the caller's default, and a flag the server did send should not look like one it did not, so the flag is now kept and reports a parse error of its own. This is also what a specification fixture for a wrongly-typed field would expect, if one is added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the per-entry parsing so Java and .NET behave identically: a field of the wrong type anywhere in the response fails the whole manifest in both. That is a real defect in both libraries, tracked as BMBB-788 and deferred rather than fixed here, so this branch is not the one place it gets fixed. The null-body guard stays, along with its test: that was a separate finding and is not part of what BMBB-788 covers. Reverts 29901b3 and 5d7f28f. The fix is preserved on lourens/bmbb-788. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves BMBB-753 and BMBB-749. Java counterpart of openfeature-provider-dotnet#96, brought up to date with .NET's current code.
What
The provider now evaluates through the v4 feature-flag endpoints, using the rules-based evaluation from #51 instead of v3's segment and rollout matching.
api/feature-flags/check/v4/andapi/feature-flags/evaluations/v4/EvaluationResponsereplacesFeatureToggles;getServerSideEvaluations()replacesgetFeatureToggleEvaluationManifest()evaluate(slug, context)dropsdefaultValueand resolves aServerSideEvaluation, so value and reason come from the server or from the rules it deferred, and a malformed response reports the problem it foundef84509→67890d8Two breaking changes
Both carry a
BREAKING CHANGE:footer, so release-please cuts the major. Cutting the release is BMBB-772.Reasonvalues. A server-resolved flag reports the service's own reason, a client-side match reportsMatched rule '<name>'., no match reportsDid not match any rules.Anything branching onReason.TARGETING_MATCHchanges silently, with no compile error.The one decision worth your attention
refactor: flatten the v4 package into the provider packageis a pure move, green on its own, and the rest of the branch sits on top of it.The switch needs the evaluator, in the provider package, to call
ServerSideEvaluation.evaluate(). Package access is not hierarchical, so av4sub-package would mean making that type andEvaluationResponsepublic — permanently, sinceOctopusProviderkeeps its fully-qualified name and so the parent→v4call outlives BMBB-780. With v4 now the only contract, the sub-package has served its purpose. Everything stays package-private, the public API is unchanged, and #51's duplicated rollout hash collapses back to one implementation.The five
fix:commits after the switch each came from a review of this branch, and each carries its own test — null entries and null bodies no longer cost the whole response, a null flag key answersFLAG_NOT_FOUNDas v3 did, aParseErrorlogs its slug, and warn-once is provider-scoped, bounded and locale-independent.One defect deliberately left in
A property of the wrong JSON type anywhere in the response still fails the whole manifest: it breaks while the response is being read, so it never reaches the per-flag reporting that contains a missing property. Tracked as BMBB-788 and deferred, so .NET and Java stay in step rather than this branch becoming the one place it is fixed — hence the
revert:commit. The fix, with tests, is parked on #54.Scope
BMBB-749 is here because it cannot be split off: the specification bump replaces its fixtures and a submodule has one pointer, so bumping first leaves v4 fixtures against a v3 provider — 80 failures, which I hit while separating the commits — and switching first leaves v4 code against deleted fixtures. .NET adopted the fixtures inside its own switch PR for the same reason.
Left to the tickets that own them, mirroring .NET's #96 → #99: BMBB-780 for the unused v3 types, still present and marked
TODO(BMBB-780), and BMBB-755 for .NET's #97 renames (BMBB-715 looks like a duplicate of it).Tests
461 pass, 0 failures, including all 83 specification tests (up from 41 — the v4 fixture set is larger) and
MalformedEvaluationTestsat 23 shapes. Public API confirmed unchanged viajavap— stillOctopusProvider,OctopusConfigurationandProductMetadata.🤖 Generated with Claude Code