Repository navigation
allow passing a decision point via in assign call - #3414
danoswaltCL wants to merge 7 commits into
Conversation
…se to just that one assignment. consolidates DecisionPoint types
There was a problem hiding this comment.
🟡 Changes recommended
The uncached scoped lookup is case-sensitive while the warm-cache lookup is not, causing previously valid decision-point lookups to fail depending on cache state.
1 open issue
What changed in this PR
Adds optional decision-point scoping to /assign and integrates it into the Java client, reducing per-request assignment work while retaining full-context fetching and experiment-pool selection.
Changes:
- Limits assignment evaluation to the relevant pool and returns only the requested decision point.
- Consolidates decision-point validation and adds scoped-assignment regression tests.
- Updates Java single-condition requests and adds a
--singleDecisionPointload-testing mode.
| File | Description |
|---|---|
packages/backend/test/unit/services/ExperimentAssignmentService.test.ts |
Tests scoped assignment, pooling, and mark consistency. |
packages/backend/test/unit/controllers/validators/ExperimentAssignmentValidator.test.ts |
Tests optional decision-point validation and target defaults. |
packages/backend/src/api/services/ThompsonSamplingRewardService.ts |
Reuses the shared decision-point reference type. |
packages/backend/src/api/services/ExperimentAssignmentService.ts |
Scopes evaluation to relevant pools and filters returned assignments. |
packages/backend/src/api/controllers/validators/RewardValidator.ts |
Uses shared nested decision-point validation. |
packages/backend/src/api/controllers/validators/MarkExperimentValidator.v6.ts |
Inherits shared decision-point validation. |
packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts |
Accepts an optional decision point. |
packages/backend/src/api/controllers/validators/DecisionPointValidator.ts |
Defines shared validation and target normalization. |
packages/backend/src/api/controllers/validators/BatchAssignValidator.ts |
Reuses shared decision-point validation. |
packages/backend/src/api/controllers/ExperimentClientController.v6.ts |
Documents and forwards the scoped-assignment parameter. |
packages/backend/CLAUDE.md |
Documents dormant preview-user behavior. |
clientlibs/java/src/main/resources/org/upgradeplatform/client/quicktest-burst-ui.html |
Adds an assignment-mode selector. |
clientlibs/java/src/main/java/org/upgradeplatform/requestbeans/ExperimentRequest.java |
Adds an optional decision-point request payload. |
clientlibs/java/src/main/java/org/upgradeplatform/client/QuickTestBurst.java |
Adds scoped-request load testing. |
clientlibs/java/src/main/java/org/upgradeplatform/client/ExperimentClient.java |
Shares request handling and scopes uncached single-condition lookups. |
clientlibs/java/CLAUDE.md |
Documents updated lookup and caching behavior. |
Review effort: Balanced
|
updated to include logic for caching rules for how |
|
Heads-up: this PR and #3408 (client lib parity) both change how the Java client's assignment cache works. A trial merge of the two conflicts in
Things that combine fine: the "no experiment" placeholder (an empty single-decision-point response still gives an Also, #3408 adds Since this PR's Java change is the smaller of the two, it's probably easiest if #3408 lands first and this one rebases onto it, using the points above as the checklist. Happy to help with the resolution. |
|
@bcb37 fyi i'm working through this now, i'll need to do a full re-testing, i'll let you know if i run into anything. i'm only planning on doing the java client library for this PR to keep the review focused; once it is merged then i'll update the other libraries based on how that gets finalized. |
…esponse Brings in client lib parity (#3408). Conflict resolution in the Java client's assignment cache: - fullContextCached is guarded by assignmentLock together with allExperiments (was a volatile flag plus synchronized methods) - invalidateUserDependentCaches() also resets fullContextCached, so a group or identity change refetches instead of returning null - isAtDecisionPoint() uses #3408's exact, null-safe match, for cache hits, lookups and mark metadata alike - getAllExperimentConditions() goes through requestAssignments(), keeping #3408's null-safe callbacks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…load requestAssignments() now uses the null-safe Consumer variant from #3408, so this branch's UnaryOperator variant has no callers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops ExperimentRequest's inner DecisionPoint in favor of the top-level bean #3408 added for rewards. A null target is now sent as "" (as the reward request does) instead of being omitted; the server treats both as the targetless decision point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers the decisionPoint request body (including a null target sent as ""), caching a fetched decision point, re-asking after an empty response, answering from a full-context cache, getAllExperimentConditions ignoring a partial cache, and a cache clear resetting the full-context flag (that last one fails if invalidateUserDependentCaches() doesn't reset fullContextCached). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CLIENT_API_COVERAGE.md: new row for /assign's decisionPoint. Java sends it from getExperimentCondition; JS and Python don't yet. - Java README: getExperimentCondition asks /assign for one decision point when the cache can't answer, and caches the result; the Caching section notes getAllExperimentConditions ignores a partial cache. - Java CLAUDE.md: fullContextCached is guarded by assignmentLock with the list and cleared with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Scoped and overlapping cold lookups can cache conflicting Thompson Sampling conditions, causing served assignments to diverge from enrollment and reward attribution or subsequent cached results.
2 open issues
1 resolved since last review
Review effort: Balanced
| } | ||
| List<ExperimentsResponse> merged = allExperiments != null ? new ArrayList<>(allExperiments) | ||
| : new ArrayList<>(); | ||
| merged.addAll(experiments); |
| requestAssignments(new ExperimentRequest(this.context, site, target), this::cacheDecisionPoint, | ||
| toAssignment); |


getExperimentConditionmethod to this new usage automaticallyAlso:
Testing showed this to be potentially 30%+ quicker /assign calls, across the board
update: this commit [cf42232](cf42232) adds a strategy for caching in client: there is a boolean flag called
fullContextCached.So, if a "full-context"
/assignis called, then a "single-decision-point"assignis called,fullContextCachedwill be true so we will look for that decision-point in the cache and not call out to the server if found.Whereas if "single-decision-point"
/assignis called first,fullContextCachedwill be false, so that if/assignis then called, it will know to reach out to server.And if "single-decision-point"
/assignis called first, and later is called again w/o a full-context/assignhaving occurred, the logic will be to look for it in the cache first and if not there, go to the server.Note the current behavior if the
assigncall comes back as[], it always refetches if it is called again (it doesn't know that[]was fetched from the server, so this didn't change that. That seems unintentional but also probably not worth changing.cf42232