Skip to content

fix: RemoteProvider cleanup#932

Merged
ayushjain17 merged 1 commit intomainfrom
fix/remote-provider
Mar 27, 2026
Merged

fix: RemoteProvider cleanup#932
ayushjain17 merged 1 commit intomainfrom
fix/remote-provider

Conversation

@ayushjain17
Copy link
Copy Markdown
Collaborator

@ayushjain17 ayushjain17 commented Mar 26, 2026

Problem

Describe the problem you are trying to solve here

Solution

Provide a brief summary of your solution so that reviewers can understand your code

Environment variable changes

What ENVs need to be added or changed

Pre-deployment activity

Things needed to be done before deploying this change (if any)

Post-deployment activity

Things needed to be done after deploying this change (if any)

API changes

Endpoint Method Request body Response Body
API GET/POST, etc request response

Possible Issues in the future

Describe any possible issues that could occur because of this change

Summary by CodeRabbit

  • New Features

    • Added optional prefix query parameter for filtering applicable variants by key prefix.
  • Changes

    • Applicable variants endpoint now uses query parameters for identifier and prefix instead of request body.
    • Configuration responses now return strongly-typed ConfigData objects instead of generic JSON documents.

@ayushjain17 ayushjain17 requested a review from a team as a code owner March 26, 2026 09:29
Copilot AI review requested due to automatic review settings March 26, 2026 09:29
@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com Bot commented Mar 26, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  crates/superposition_provider/examples/local_http_example.rs  98% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java  96% smaller
  docs/docs/api/applicable-variants.RequestSchema.json  96% smaller
  clients/python/sdk/superposition_sdk/_private/schemas.py  76% smaller
  clients/python/sdk/superposition_sdk/models.py  74% smaller
  docs/docs/api/applicable-variants.ParamsDetails.json  74% smaller
  clients/javascript/sdk/src/models/models_0.ts  69% smaller
  docs/docs/api/list-versions.StatusCodes.json  64% smaller
  docs/docs/api/get-version.StatusCodes.json  63% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java  63% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/Config.java  54% smaller
  crates/experimentation_platform/src/api/experiments/handlers.rs  53% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java  39% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java  39% smaller
  crates/superposition_provider/src/local_provider.rs  26% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java  21% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/ListVersionsMember.java  21% smaller
  crates/superposition_provider/src/traits.rs  20% smaller
  crates/superposition_sdk/src/types.rs  17% smaller
  docs/docs/api/Superposition.openapi.json  14% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/ApplicableVariantsInput.java  13% smaller
  crates/superposition_provider/src/remote_provider.rs  8% smaller
  crates/superposition_sdk/src/serde_util.rs  6% smaller
  clients/javascript/sdk/src/commands/ListVersionsCommand.ts  5% smaller
  clients/javascript/sdk/src/commands/GetVersionCommand.ts  5% smaller
  clients/haskell/sdk/Io/Superposition/Model/ApplicableVariantsInput.hs Unsupported file format
  clients/haskell/sdk/Io/Superposition/Model/ConfigData.hs Unsupported file format
  clients/haskell/sdk/Io/Superposition/Model/GetVersionOutput.hs Unsupported file format
  clients/haskell/sdk/Io/Superposition/Model/ListVersionsMember.hs Unsupported file format
  clients/haskell/sdk/SuperpositionSDK.cabal Unsupported file format
  clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigData.java  0% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigVersion.java  0% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigOutput.java  0% smaller
  clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigWithIdentifierOutput.java  0% smaller
  clients/javascript/sdk/src/commands/ApplicableVariantsCommand.ts  0% smaller
  clients/javascript/sdk/src/protocols/Aws_restJson1.ts  0% smaller
  clients/python/sdk/superposition_sdk/serialize.py  0% smaller
  crates/superposition/src/resolve/handlers.rs  0% smaller
  crates/superposition_provider/src/conversions.rs  0% smaller
  crates/superposition_sdk/src/client/applicable_variants.rs  0% smaller
  crates/superposition_sdk/src/client/get_version.rs  0% smaller
  crates/superposition_sdk/src/operation/applicable_variants.rs  0% smaller
  crates/superposition_sdk/src/operation/applicable_variants/_applicable_variants_input.rs Unsupported file format
  crates/superposition_sdk/src/operation/applicable_variants/builders.rs Unsupported file format
  crates/superposition_sdk/src/operation/get_version/_get_version_output.rs Unsupported file format
  crates/superposition_sdk/src/protocol_serde.rs  0% smaller
  crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs  0% smaller
  crates/superposition_sdk/src/protocol_serde/shape_config_data.rs  0% smaller
  crates/superposition_sdk/src/protocol_serde/shape_get_version.rs  0% smaller
  crates/superposition_sdk/src/protocol_serde/shape_list_versions_member.rs  0% smaller
  crates/superposition_sdk/src/types/_config_data.rs Unsupported file format
  crates/superposition_sdk/src/types/_list_versions_member.rs Unsupported file format
  crates/superposition_sdk/src/types/builders.rs  0% smaller
  crates/superposition_types/src/api/experiments.rs  0% smaller
  docs/docs/api/add-members-to-group.api.mdx Unsupported file format
  docs/docs/api/applicable-variants.api.mdx Unsupported file format
  docs/docs/api/bulk-operation.api.mdx Unsupported file format
  docs/docs/api/conclude-experiment.api.mdx Unsupported file format
  docs/docs/api/create-context.api.mdx Unsupported file format
  docs/docs/api/create-default-config.api.mdx Unsupported file format
  docs/docs/api/create-dimension.api.mdx Unsupported file format
  docs/docs/api/create-experiment-group.api.mdx Unsupported file format
  docs/docs/api/create-experiment.api.mdx Unsupported file format
  docs/docs/api/create-function.api.mdx Unsupported file format
  docs/docs/api/create-organisation.api.mdx Unsupported file format
  docs/docs/api/create-secret.api.mdx Unsupported file format
  docs/docs/api/create-type-templates.api.mdx Unsupported file format
  docs/docs/api/create-variable.api.mdx Unsupported file format
  docs/docs/api/create-webhook.api.mdx Unsupported file format
  docs/docs/api/create-workspace.api.mdx Unsupported file format
  docs/docs/api/delete-context.api.mdx Unsupported file format
  docs/docs/api/delete-default-config.api.mdx Unsupported file format
  docs/docs/api/delete-dimension.api.mdx Unsupported file format
  docs/docs/api/delete-experiment-group.api.mdx Unsupported file format
  docs/docs/api/delete-function.api.mdx Unsupported file format
  docs/docs/api/delete-secret.api.mdx Unsupported file format
  docs/docs/api/delete-type-templates.api.mdx Unsupported file format
  docs/docs/api/delete-variable.api.mdx Unsupported file format
  docs/docs/api/delete-webhook.api.mdx Unsupported file format
  docs/docs/api/discard-experiment.api.mdx Unsupported file format
  docs/docs/api/get-config-json.api.mdx Unsupported file format
  docs/docs/api/get-config-toml.api.mdx Unsupported file format
  docs/docs/api/get-config.api.mdx Unsupported file format
  docs/docs/api/get-context-from-condition.api.mdx Unsupported file format
  docs/docs/api/get-context.api.mdx Unsupported file format
  docs/docs/api/get-default-config.api.mdx Unsupported file format
  docs/docs/api/get-dimension.api.mdx Unsupported file format
  docs/docs/api/get-experiment-config.api.mdx Unsupported file format
  docs/docs/api/get-experiment-group.api.mdx Unsupported file format
  docs/docs/api/get-experiment.api.mdx Unsupported file format
  docs/docs/api/get-function.api.mdx Unsupported file format
  docs/docs/api/get-organisation.api.mdx Unsupported file format
  docs/docs/api/get-resolved-config-with-identifier.api.mdx Unsupported file format
  docs/docs/api/get-resolved-config.api.mdx Unsupported file format
  docs/docs/api/get-secret.api.mdx Unsupported file format
  docs/docs/api/get-type-template.api.mdx Unsupported file format
  docs/docs/api/get-type-templates-list.api.mdx Unsupported file format
  docs/docs/api/get-variable.api.mdx Unsupported file format
  docs/docs/api/get-version.api.mdx Unsupported file format
  docs/docs/api/get-webhook-by-event.api.mdx Unsupported file format
  docs/docs/api/get-webhook.api.mdx Unsupported file format
  docs/docs/api/get-workspace.api.mdx Unsupported file format
  docs/docs/api/list-audit-logs.api.mdx Unsupported file format
  docs/docs/api/list-contexts.api.mdx Unsupported file format
  docs/docs/api/list-default-configs.api.mdx Unsupported file format
  docs/docs/api/list-dimensions.api.mdx Unsupported file format
  docs/docs/api/list-experiment-groups.api.mdx Unsupported file format
  docs/docs/api/list-experiment.api.mdx Unsupported file format
  docs/docs/api/list-function.api.mdx Unsupported file format
  docs/docs/api/list-organisation.api.mdx Unsupported file format
  docs/docs/api/list-secrets.api.mdx Unsupported file format
  docs/docs/api/list-variables.api.mdx Unsupported file format
  docs/docs/api/list-versions.api.mdx Unsupported file format
  docs/docs/api/list-webhook.api.mdx Unsupported file format
  docs/docs/api/list-workspace.api.mdx Unsupported file format
  docs/docs/api/migrate-workspace-schema.api.mdx Unsupported file format
  docs/docs/api/move-context.api.mdx Unsupported file format
  docs/docs/api/pause-experiment.api.mdx Unsupported file format
  docs/docs/api/publish.api.mdx Unsupported file format
  docs/docs/api/ramp-experiment.api.mdx Unsupported file format
  docs/docs/api/remove-members-from-group.api.mdx Unsupported file format
  docs/docs/api/resume-experiment.api.mdx Unsupported file format
  docs/docs/api/rotate-master-encryption-key.api.mdx Unsupported file format
  docs/docs/api/rotate-workspace-encryption-key.api.mdx Unsupported file format
  docs/docs/api/test.api.mdx Unsupported file format
  docs/docs/api/update-default-config.api.mdx Unsupported file format
  docs/docs/api/update-dimension.api.mdx Unsupported file format
  docs/docs/api/update-experiment-group.api.mdx Unsupported file format
  docs/docs/api/update-function.api.mdx Unsupported file format
  docs/docs/api/update-organisation.api.mdx Unsupported file format
  docs/docs/api/update-override.api.mdx Unsupported file format
  docs/docs/api/update-overrides-experiment.api.mdx Unsupported file format
  docs/docs/api/update-secret.api.mdx Unsupported file format
  docs/docs/api/update-type-templates.api.mdx Unsupported file format
  docs/docs/api/update-variable.api.mdx Unsupported file format
  docs/docs/api/update-webhook.api.mdx Unsupported file format
  docs/docs/api/update-workspace.api.mdx Unsupported file format
  docs/docs/api/validate-context.api.mdx Unsupported file format
  docs/docs/api/weight-recompute.api.mdx Unsupported file format
  smithy/models/config.smithy Unsupported file format
  smithy/models/experiments.smithy Unsupported file format

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 569bd0de-5c8a-4e1a-b394-cedb1b9414c9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces a new typed ConfigData model across multiple language SDKs (Haskell, Java, JavaScript, Python, Rust) and Smithy source models, replacing untyped Document/Value fields in config responses. The ApplicableVariantsInput request structure is modified to move identifier from request body to query parameters and adds an optional prefix query parameter for filtering variants by key prefix.

Changes

Cohort / File(s) Summary
ConfigData Type Definition
smithy/models/config.smithy, crates/superposition_sdk/src/types/_config_data.rs, clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigData.java, clients/haskell/sdk/Io/Superposition/Model/ConfigData.hs, clients/python/sdk/superposition_sdk/models.py
New ConfigData structure introduced with required fields: contexts, overrides, default_configs, dimensions. Includes builder pattern implementations and serialization/deserialization support across all language SDKs.
GetVersionOutput & ListVersionsMember Config Field Updates
smithy/models/config.smithy, crates/superposition_sdk/src/operation/get_version/_get_version_output.rs, crates/superposition_sdk/src/types/_list_versions_member.rs, clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java, clients/java/sdk/src/main/java/io/juspay/superposition/model/ListVersionsMember.java, clients/haskell/sdk/Io/Superposition/Model/GetVersionOutput.hs, clients/haskell/sdk/Io/Superposition/Model/ListVersionsMember.hs, clients/javascript/sdk/src/models/models_0.ts, clients/javascript/sdk/src/commands/GetVersionCommand.ts, clients/javascript/sdk/src/commands/ListVersionsCommand.ts, clients/python/sdk/superposition_sdk/models.py
Changed config field type from untyped Document/Value to concrete ConfigData across GetVersionOutput and ListVersionsMember models in all SDKs. Updated accessors, builders, and serialization/deserialization logic accordingly.
ApplicableVariantsInput Parameter Restructuring
smithy/models/experiments.smithy, crates/superposition_sdk/src/operation/applicable_variants/_applicable_variants_input.rs, crates/superposition_sdk/src/operation/applicable_variants/builders.rs, crates/superposition_types/src/api/experiments.rs, clients/haskell/sdk/Io/Superposition/Model/ApplicableVariantsInput.hs, clients/java/sdk/src/main/java/io/juspay/superposition/model/ApplicableVariantsInput.java, clients/javascript/sdk/src/models/models_0.ts, clients/python/sdk/superposition_sdk/models.py
Moved identifier from request body to HTTP query parameter; added optional prefix query parameter (array of strings). Updated all SDK models, builders, and serialization to reflect parameter mapping changes.
Protocol Serialization & Deserialization
crates/superposition_sdk/src/protocol_serde.rs, crates/superposition_sdk/src/protocol_serde/shape_config_data.rs, crates/superposition_sdk/src/protocol_serde/shape_get_version.rs, crates/superposition_sdk/src/protocol_serde/shape_list_versions_member.rs, crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs, clients/javascript/sdk/src/protocols/Aws_restJson1.ts, clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigVersion.java, clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java, clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java
Updated serialization/deserialization routines to handle ConfigData type and query parameter routing for ApplicableVariantsInput. Added de_config_data and related helpers; removed identifier field serialization from request body; updated deserializers to construct ConfigData from structured tokens.
Rust SDK Type & Builder Infrastructure
crates/superposition_sdk/src/types.rs, crates/superposition_sdk/src/types/builders.rs, crates/superposition_sdk/src/serde_util.rs, crates/superposition_sdk/src/operation/applicable_variants.rs
Added ConfigData and ConfigDataBuilder to public type re-exports; extended serde_util with config_data_correct_errors helper; implemented URI query string builder for ApplicableVariantsInput with identifier and prefix parameters.
Request Handler Updates
crates/experimentation_platform/src/api/experiments/handlers.rs, crates/superposition/src/resolve/handlers.rs, crates/superposition_provider/src/local_provider.rs, crates/superposition_provider/src/remote_provider.rs, crates/superposition_provider/src/traits.rs, crates/superposition_provider/src/conversions.rs, crates/superposition_provider/examples/local_http_example.rs
Refactored handlers to accept prefix_filter as optional owned Vec<String> instead of borrowed slice references; updated get_applicable_variants_helper to accept context by reference and optional prefix filter; implemented prefix-based experiment filtering; removed in-memory response cache from remote provider; added context conversion helpers for Document types.
API Documentation & OpenAPI Spec
docs/docs/api/Superposition.openapi.json, docs/docs/api/applicable-variants.ParamsDetails.json, docs/docs/api/applicable-variants.RequestSchema.json, docs/docs/api/applicable-variants.api.mdx, docs/docs/api/get-version.StatusCodes.json, docs/docs/api/get-version.api.mdx, docs/docs/api/list-versions.StatusCodes.json, docs/docs/api/list-versions.api.mdx
Updated OpenAPI specification to document ConfigData schema and ApplicableVariants endpoint with query parameters (identifier, prefix). Removed identifier from ApplicableVariantsRequest body schema; updated response examples and status code definitions to reflect structured ConfigData type.
Java SDK Auxiliary Models
clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java, clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java, clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java, clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java, clients/java/sdk/src/main/java/io/juspay/superposition/model/Config.java, clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigOutput.java, clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigWithIdentifierOutput.java
Reordered authorization scheme declarations and type registry entries (no functional impact); updated Config resource properties to expose contexts, overrides, default_configs, dimensions instead of generic config; adjusted trait ordering in GetResolvedConfig output schemas.
Haskell SDK Build Configuration
clients/haskell/sdk/SuperpositionSDK.cabal
Added Io.Superposition.Model.ConfigData to exposed modules list; reordered module declarations for consistency.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, P1, API

Suggested reviewers

  • Datron
  • mahatoankitkumar
  • sauraww

Poem

🐰 A typed config hops into view,
No more untyped Documents through and through!
Query strings now carry identifier and prefix with pride,
SDKs celebrate—the schema's our guide! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "fix: RemoteProvider cleanup" is vague and generic; it uses a non-descriptive term "cleanup" that does not clearly convey what is being fixed or refactored. Replace "cleanup" with a specific description of the primary change, such as "fix: RemoteProvider remove cache implementation" or "fix: RemoteProvider migrate to SDK prefix filtering".
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/remote-provider

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Superposition API contract and SDKs to (1) move ApplicableVariants’s identifier into query params (and add an optional prefix filter), and (2) replace config: Document with a typed ConfigData shape for config version metadata responses. It also cleans up the Rust remote provider to call the identifier-based resolved-config endpoint and to persist the OpenFeature initialization context.

Changes:

  • ApplicableVariants: identifier moved from request body to required query param; added optional repeated prefix query param.
  • GetVersion / ListVersions: config changed from untyped Document to structured ConfigData across models + generated SDKs/docs.
  • Rust provider updates: remote provider uses get_resolved_config_with_identifier and threads prefix through filtering APIs; stores global context on initialize.

Reviewed changes

Copilot reviewed 61 out of 61 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
smithy/models/experiments.smithy Moves ApplicableVariantsInput.identifier to @httpQuery and adds prefix query list.
smithy/models/config.smithy Introduces ConfigData; updates resource properties and version metadata to use structured config.
docs/docs/api/list-versions.api.mdx Regenerates API doc blob for ListVersions after schema changes.
docs/docs/api/list-versions.StatusCodes.json Updates response schema to show config as ConfigData.
docs/docs/api/get-version.api.mdx Regenerates API doc blob for GetVersion after schema changes.
docs/docs/api/get-version.StatusCodes.json Updates response schema to show config as ConfigData.
docs/docs/api/applicable-variants.api.mdx Regenerates API doc blob reflecting query-based identifier and new prefix.
docs/docs/api/applicable-variants.RequestSchema.json Removes identifier from request body schema (now query param).
docs/docs/api/applicable-variants.ParamsDetails.json Adds identifier (required) and prefix (optional) query params.
docs/docs/api/Superposition.openapi.json Updates OpenAPI: query params for ApplicableVariants, adds ConfigData schema, and swaps version metadata config to ConfigData.
crates/superposition_types/src/api/experiments.rs Updates Rust API types: query now supports prefix; request body no longer includes identifier.
crates/superposition_sdk/src/types/builders.rs Exposes ConfigDataBuilder in the Rust SDK builders module.
crates/superposition_sdk/src/types/_list_versions_member.rs Switches ListVersionsMember.config from Document to ConfigData.
crates/superposition_sdk/src/types/_config_data.rs Adds generated Rust SDK type ConfigData.
crates/superposition_sdk/src/types.rs Re-exports ConfigData and wires in the new module.
crates/superposition_sdk/src/serde_util.rs Adds error-correction defaults for ConfigData and updates version shapes to use it.
crates/superposition_sdk/src/protocol_serde/shape_list_versions_member.rs Deserializes config as ConfigData instead of Document.
crates/superposition_sdk/src/protocol_serde/shape_get_version.rs Deserializes config as ConfigData instead of Document.
crates/superposition_sdk/src/protocol_serde/shape_config_data.rs Adds generated deserializer for ConfigData.
crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs Stops serializing identifier in the request body.
crates/superposition_sdk/src/protocol_serde.rs Registers shape_config_data module for protocol serde.
crates/superposition_sdk/src/operation/get_version/_get_version_output.rs Switches GetVersionOutput.config from Document to ConfigData.
crates/superposition_sdk/src/operation/applicable_variants/builders.rs Adds fluent builder methods for prefix.
crates/superposition_sdk/src/operation/applicable_variants/_applicable_variants_input.rs Adds prefix to operation input type and builder.
crates/superposition_sdk/src/operation/applicable_variants.rs Adds query-string serialization for identifier (required) and prefix.
crates/superposition_sdk/src/client/get_version.rs Updates client docs to reference ConfigData in the output.
crates/superposition_sdk/src/client/applicable_variants.rs Updates client docs to include optional prefix input.
crates/superposition_provider/src/traits.rs Extends provider traits to accept an owned prefix_filter vector and adds it to applicable-variants meta API.
crates/superposition_provider/src/remote_provider.rs Removes response caching; switches to identifier-based resolved-config endpoint; threads prefix and stores global context on initialize.
crates/superposition_provider/src/local_provider.rs Propagates the new prefix_filter type/signature through local provider evaluation and variant selection.
crates/superposition_provider/src/conversions.rs Adds EvaluationContextFieldValue -> Document conversion and a query_document helper for SDK calls.
crates/superposition_provider/examples/local_http_example.rs Updates example to pass the new prefix_filter argument.
crates/superposition/src/resolve/handlers.rs Plumbs prefix filter through to experiment variant resolution helper.
crates/experimentation_platform/src/api/experiments/handlers.rs Adds server-side prefix filtering for applicable variants and switches handler to query-based identifier.
clients/python/sdk/superposition_sdk/serialize.py Adds query param serialization for identifier and prefix on ApplicableVariants.
clients/python/sdk/superposition_sdk/models.py Introduces ConfigData model; updates version outputs to use it; updates applicable variants input to include prefix and omit identifier from body.
clients/python/sdk/superposition_sdk/_private/schemas.py Updates Smithy Python schemas for query-based identifier/prefix and adds ConfigData shape.
clients/javascript/sdk/src/protocols/Aws_restJson1.ts Moves ApplicableVariants.identifier to query, adds prefix query, and deserializes version configs as ConfigData.
clients/javascript/sdk/src/models/models_0.ts Adds prefix to applicable variants input; introduces ConfigData; updates version response members.
clients/javascript/sdk/src/commands/ListVersionsCommand.ts Updates JSDoc example to show structured ConfigData in config.
clients/javascript/sdk/src/commands/GetVersionCommand.ts Updates JSDoc example to show structured ConfigData in config.
clients/javascript/sdk/src/commands/ApplicableVariantsCommand.ts Updates JSDoc example to include prefix.
clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java Reorders/adjusts serde helpers after new/relocated shapes (incl. context map + dependency graph).
clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java Reorders schema declarations and wires in CONTEXT_MAP placement changes.
clients/java/sdk/src/main/java/io/juspay/superposition/model/ListVersionsMember.java Switches config from Document to ConfigData and updates serde.
clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java Switches config from Document to ConfigData and updates serde.
clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigWithIdentifierOutput.java Trait order adjustment for payload/required on config.
clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigOutput.java Trait order adjustment for payload/required on config.
clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigVersion.java Updates config property schema to ConfigData.
clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigData.java Adds generated Java model ConfigData.
clients/java/sdk/src/main/java/io/juspay/superposition/model/Config.java Updates resource properties to include structured config fields instead of a single config document.
clients/java/sdk/src/main/java/io/juspay/superposition/model/ApplicableVariantsInput.java Adds prefix and marks identifier as HttpQuery; updates serialization accordingly.
clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java Reorders type registry entries (no semantic change).
clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java Reorders auth scheme declarations (no semantic change).
clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java Reorders type registry entries (no semantic change).
clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java Reorders auth scheme declarations (no semantic change).
clients/haskell/sdk/SuperpositionSDK.cabal Exposes the new ConfigData module and adjusts export ordering.
clients/haskell/sdk/Io/Superposition/Model/ListVersionsMember.hs Switches config from Aeson.Value to ConfigData.
clients/haskell/sdk/Io/Superposition/Model/GetVersionOutput.hs Switches config from Aeson.Value to ConfigData.
clients/haskell/sdk/Io/Superposition/Model/ConfigData.hs Adds generated Haskell model ConfigData.
clients/haskell/sdk/Io/Superposition/Model/ApplicableVariantsInput.hs Adds prefix and serializes identifier/prefix as query params rather than body fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/superposition/src/resolve/handlers.rs Outdated
Comment on lines +983 to +987
let context = if req.method() == actix_web::http::Method::GET {
dimension_params.into_inner()
} else {
req_body.map_or_else(QueryMap::default, |body| body.into_inner().context.into())
};
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For POST requests, ApplicableVariantsInput’s context is marked @required in the Smithy model, but this handler currently allows req_body to be None and silently uses an empty default context. Consider returning a bad argument error when req.method() != GET and req_body is missing (or make the extractor non-optional) to keep the API contract consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +65
let (query_data, targeting_key) =
conversions::evaluation_context_to_query_document(context);

// 4. Build and send the get_resolved_config request
// Always fetch WITHOUT prefix filter so we can cache the full result
let mut builder = client
.get_resolved_config()
let response = self
.client
.get_resolved_config_with_identifier()
.workspace_id(&self.options.workspace_id)
.org_id(&self.options.org_id);

// Set context dimensions from evaluation context
let sdk_context: HashMap<String, Document> = query_data
.into_iter()
.map(|(k, v)| (k, conversions::value_to_document(v)))
.collect();
builder = builder.set_context(Some(sdk_context));

// NOTE: We intentionally do NOT set prefix filter on the SDK request
// so we always get the full config and can cache it. Prefix filtering
// is applied locally after caching.

let response = builder.send().await.map_err(|e| {
SuperpositionError::NetworkError(format!(
"Failed to get resolved config: {}",
e
))
})?;
.org_id(&self.options.org_id)
.set_context(Some(query_data))
.set_identifier(targeting_key)
.set_prefix(prefix_filter)
.send()
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_resolved_config_with_identifier requires a non-empty identifier, but this code forwards targeting_key: Option<String> directly into set_identifier(...). If the incoming/global EvaluationContext lacks a targeting key, the request will fail at build time and be surfaced as a NetworkError. Consider validating targeting_key (Some + non-empty) up front and returning a more appropriate error (or deriving a fallback identifier) before calling .send().

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please re-check

Comment on lines +119 to +130
let (query_data, targeting_key) =
conversions::evaluation_context_to_query_document(context);

let applicable_variants = self
.client
.applicable_variants()
.workspace_id(&self.options.workspace_id)
.org_id(&self.options.org_id)
.set_context(Some(query_data))
.set_identifier(targeting_key)
.set_prefix(prefix_filter)
.send()
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here: .set_identifier(targeting_key) forwards an Option<String> into an operation where identifier is required/non-empty. If targeting_key is missing, this becomes a build-time failure but is mapped into a NetworkError. Validate targeting_key earlier and return a clearer error (or require callers to provide it) before sending the request.

Copilot uses AI. Check for mistakes.
Comment thread crates/experimentation_platform/src/api/experiments/handlers.rs Fixed
Comment thread crates/superposition/src/resolve/handlers.rs Fixed
@ayushjain17 ayushjain17 force-pushed the fix/remote-provider branch from f7d27ba to abca9bc Compare March 26, 2026 09:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java (1)

283-285: ⚠️ Potential issue | 🟠 Major

errorCorrection() marks config present without creating it.

Line 211 makes config non-nullable, but Line 284 only flips the presence tracker. After errorCorrection().build(), GetVersionOutput can still carry config == null, and Line 128 then quietly omits a required field during serialization. Populate a corrected ConfigData instance here instead of only setting the tracker.

🛠 Suggested fix
             if (!tracker.checkMember($SCHEMA_CONFIG)) {
-                tracker.setMember($SCHEMA_CONFIG);
+                config(ConfigData.builder().errorCorrection().build());
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java`
around lines 283 - 285, In errorCorrection() for GetVersionOutput, the code
currently flips the presence tracker via tracker.setMember($SCHEMA_CONFIG)
without actually populating the config field, leaving config nullable despite
being marked non-nullable; update errorCorrection() to create and assign a
default ConfigData instance (e.g., new ConfigData(...) or
ConfigData.builder().build()) to the config member before calling
tracker.setMember($SCHEMA_CONFIG) so that after errorCorrection().build() the
config field is non-null and will be serialized correctly.
docs/docs/api/Superposition.openapi.json (2)

2604-2613: ⚠️ Potential issue | 🟠 Major

Make the POST body optional in the spec.

Line 2612 still marks requestBody as required, but the handler accepts Option<Json<ApplicableVariantsRequest>> and falls back to an empty context when no body is sent. After moving identifier to the query string, that makes generated clients stricter than the server for valid query-only calls.

💡 Suggested change
                 "requestBody": {
                     "content": {
                         "application/json": {
                             "schema": {
                                 "$ref": "#/components/schemas/ApplicableVariantsRequestContent"
                             }
                         }
-                    },
-                    "required": true
+                    }
                 },

Also applies to: 6118-6127

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/api/Superposition.openapi.json` around lines 2604 - 2613, The
OpenAPI spec marks the POST operation's requestBody as required but the server
handler accepts Option<Json<ApplicableVariantsRequest>> and treats an absent
body as an empty context; update the operation(s) (the requestBody for the
ApplicableVariantsRequestContent schema) to set "required": false (or remove the
required flag) so generated clients may omit the body when using query-only
calls; search for occurrences of ApplicableVariantsRequestContent and the
requestBody block and make the change consistently (also apply the same fix to
the other occurrence around the ApplicableVariantsRequestContent reference at
the later location).

2600-2677: ⚠️ Potential issue | 🟡 Minor

Document the GET form of /experiments/applicable-variants.

Line 2601 only exposes post, while crates/experimentation_platform/src/api/experiments/handlers.rs:970-1000 still registers both GET and POST for this path. If the GET route is public, it should be described here too; otherwise the published docs and generated clients will continue to hide a supported entry point.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/api/Superposition.openapi.json` around lines 2600 - 2677, The
OpenAPI spec currently documents only the POST for
"/experiments/applicable-variants" (operationId "ApplicableVariants") while the
Rust handlers (crates/experimentation_platform/src/api/experiments/handlers.rs)
register both GET and POST; add a GET operation entry for
"/experiments/applicable-variants" that mirrors the POST (same parameters: query
"identifier", query "prefix" array, headers "x-org-id" and "x-workspace"; same
request/response schemas or if GET is truly query-only, move the request body
into query schema accordingly) and include the same 200 and 500 responses so the
public docs and generated clients expose the GET endpoint used by the
ApplicableVariants handler.
🧹 Nitpick comments (4)
crates/superposition_provider/src/conversions.rs (1)

119-145: Keep remote context normalization aligned with the local path.

evaluation_context_to_value already sanitizes NaN/±Inf, but this new Document path forwards them unchanged. The same EvaluationContext can now be normalized differently by local vs remote providers.

♻️ Suggested simplification
 pub fn evaluation_context_to_document(value: EvaluationContextFieldValue) -> Document {
-    match value {
-        EvaluationContextFieldValue::Bool(b) => Document::Bool(b),
-        EvaluationContextFieldValue::Int(i) => {
-            Document::Number(aws_smithy_types::Number::NegInt(i))
-        }
-        EvaluationContextFieldValue::Float(f) => {
-            Document::Number(aws_smithy_types::Number::Float(f))
-        }
-        EvaluationContextFieldValue::String(s) => Document::String(s),
-        EvaluationContextFieldValue::DateTime(dt) => Document::String(dt.to_string()),
-        EvaluationContextFieldValue::Struct(s) => {
-            let struct_map = s
-                .downcast_ref::<HashMap<String, EvaluationContextFieldValue>>()
-                .map(|m| {
-                    m.iter()
-                        .map(|(k, v)| (k.clone(), evaluation_context_to_document(v.clone())))
-                        .collect()
-                })
-                .unwrap_or_else(|| {
-                    log::warn!("Failed to downcast struct value to expected HashMap format, got {:?}. Returning empty object.", s.type_id());
-                    HashMap::new()
-                });
-            Document::Object(struct_map)
-        }
-    }
+    value_to_document(evaluation_context_to_value(value))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/superposition_provider/src/conversions.rs` around lines 119 - 145, The
Document path (evaluation_context_to_document) is not normalizing Float values
the way evaluation_context_to_value does, causing inconsistent normalization for
NaN/±Inf; update evaluation_context_to_document so floats are sanitized the same
way as evaluation_context_to_value (either call/evaluate via
evaluation_context_to_value then convert that Value to a Document, or replicate
the exact NaN/±Inf normalization logic used in evaluation_context_to_value) for
EvaluationContextFieldValue::Float (and any numeric Document::Number conversion)
so remote and local providers produce identical normalized representations.
docs/docs/api/Superposition.openapi.json (1)

6350-6375: Reuse ConfigData for GetConfigResponseContent too.

GetConfigResponseContent still duplicates this schema field-for-field. Pointing it at ConfigData would remove a second copy of the same contract and reduce the chance that live-config and versioned-config payloads drift apart later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/api/Superposition.openapi.json` around lines 6350 - 6375, Get rid
of the duplicated schema by updating the OpenAPI component that defines
GetConfigResponseContent to reference the existing ConfigData schema instead of
repeating its fields; locate the GetConfigResponseContent component and replace
its inline object definition with a $ref to "#/components/schemas/ConfigData" so
both live-config and versioned-config use the single ConfigData contract
(symbols to edit: ConfigData, GetConfigResponseContent).
clients/python/sdk/superposition_sdk/models.py (2)

2553-2569: Duplicate implementation of _serialize_object/_deserialize_object.

_serialize_context_map and _deserialize_context_map are identical to _serialize_object and _deserialize_object defined at lines 2455-2471. Consider reusing the existing functions to avoid duplication.

♻️ Proposed fix to remove duplication
-def _serialize_context_map(serializer: ShapeSerializer, schema: Schema, value: dict[str, Document]) -> None:
-    with serializer.begin_map(schema, len(value)) as m:
-        value_schema = schema.members["value"]
-        for k, v in value.items():
-            m.entry(k, lambda vs: vs.write_document(value_schema, v))
-
-def _deserialize_context_map(deserializer: ShapeDeserializer, schema: Schema) -> dict[str, Document]:
-    result: dict[str, Document] = {}
-    value_schema = schema.members["value"]
-    def _read_value(k: str, d: ShapeDeserializer):
-        if d.is_null():
-            d.read_null()
-
-        else:
-            result[k] = d.read_document(value_schema)
-    deserializer.read_map(schema, _read_value)
-    return result

Then replace usages of _serialize_context_map/_deserialize_context_map with _serialize_object/_deserialize_object.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/python/sdk/superposition_sdk/models.py` around lines 2553 - 2569, The
two functions _serialize_context_map and _deserialize_context_map duplicate
logic already implemented in _serialize_object and _deserialize_object; remove
the duplicate functions, update any references to call _serialize_object and
_deserialize_object instead (ensure the same parameter ordering and types are
preserved when replacing calls), and run tests to confirm behavior; if any
schema/member names differ, adapt the call by passing the same
schema.members["value"] and value dict to _serialize_object/_deserialize_object
so behavior remains identical.

2455-2471: Consider using default argument to capture loop variable in lambda.

The lambda lambda vs: vs.write_document(value_schema, v) captures v by reference. While this typically works because m.entry calls the lambda immediately, the idiomatic approach to avoid late-binding closure issues is to capture by value using a default argument.

♻️ Proposed defensive fix
         for k, v in value.items():
-            m.entry(k, lambda vs: vs.write_document(value_schema, v))
+            m.entry(k, lambda vs, v=v: vs.write_document(value_schema, v))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/python/sdk/superposition_sdk/models.py` around lines 2455 - 2471, In
_serialize_object, the inline lambda passed to m.entry currently closes over the
loop variable v which can cause late-binding issues; change the lambda in
function _serialize_object (used with ShapeSerializer.m.entry) to capture v by
value via a default argument (e.g., lambda vs, v=v: ...) so each entry writes
the correct document value immediately when invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigData.java`:
- Around line 46-50: The constructor in ConfigData currently stores unmodifiable
views of the Builder's collections but does not defensively copy them, so
subsequent mutations of builder.contexts, builder.defaultConfigs,
builder.dimensions, or the maps inside builder.overrides can mutate the supposed
immutable model and break equals()/hashCode(). Fix by making defensive copies
before wrapping: e.g., copy builder.contexts into a new List and wrap with
Collections.unmodifiableList, copy builder.defaultConfigs and builder.dimensions
into new Maps and wrap with Collections.unmodifiableMap, and for overrides
create a new Map whose values are copies of each inner Map (and make those inner
maps unmodifiable) before wrapping the outer map as unmodifiable; update the
ConfigData(Builder builder) assignments for contexts, overrides, defaultConfigs,
and dimensions accordingly (referencing ConfigData constructor, fields
contexts/overrides/defaultConfigs/dimensions and
builder.contexts/builder.overrides).

In `@clients/javascript/sdk/src/protocols/Aws_restJson1.ts`:
- Around line 5512-5525: de_ConfigData currently calls deserializers (de_Object,
de_OverridesMap, de_ContextList, de_DimensionData) that drop explicit null map
entries, causing stored nulls (e.g. default_configs.foo = null) to disappear;
update de_ConfigData to use null-preserving deserializers instead: replace the
de_Object call for 'default_configs' with a null-preserving document map
deserializer (e.g. de_ObjectPreserveNull), replace the 'overrides' call with a
null-preserving overrides map deserializer (e.g. de_OverridesMapPreserveNull),
and ensure de_ContextList and de_DimensionData use/compose null-preserving
variants for contexts[*].condition and dimensions[*].schema (or implement
helpers de_ContextListPreserveNull and de_DimensionDataPreserveNull if needed)
so explicit null entries are retained through GetVersion/ListVersions responses.

In `@crates/experimentation_platform/src/api/experiments/handlers.rs`:
- Around line 995-996: The handler is passing query_data.prefix directly into
get_applicable_variants_helper which allows unbounded user-controlled lists to
be copied and turned into a HashSet; instead, validate and bind a sanitized
prefix before passing it: inspect query_data.prefix, enforce a maximum number of
entries and maximum element byte/char length (or reject oversized requests),
truncate elements if desired, and build a new Vec/String collection (e.g., a
sanitized_prefix variable) to pass instead of prefix.map(|p| p.0); ensure this
validation occurs in the same scope that constructs the arguments for
get_applicable_variants_helper and use the sanitized identifier/prefix variables
when calling the helper.
- Around line 983-987: The GET branch builds context from
DimensionQuery<QueryMap> which currently folds control params like "identifier",
"toss" and "prefix" into the evaluation context and alters bucketing; change the
GET branch so after calling dimension_params.into_inner() you explicitly remove
those control query keys (e.g. filter out "identifier", "toss", "prefix" from
the returned QueryMap) before assigning to context, keeping the non-GET req_body
handling (req_body.map_or_else(...)) unchanged; reference DimensionQuery,
QueryMap, the context variable, and the req.method() check to locate where to
perform the filtering.

In `@crates/superposition_provider/src/remote_provider.rs`:
- Around line 119-130: Short-circuit when the targeting_key returned by
conversions::evaluation_context_to_query_document(context) is None instead of
calling .set_identifier(None) on the request: check the targeting_key Option
before constructing the self.client.applicable_variants() request, and if it's
None return the appropriate local outcome (e.g. an early Ok result for no
applicable variants or a clear domain-level error) so you never send a request
with set_identifier(None) that would produce a remote 400/NetworkError.

In `@smithy/models/config.smithy`:
- Around line 79-91: The schema incorrectly marks ConfigData members as required
(contexts, overrides, default_configs, dimensions) while the Rust SDK still
auto-fills missing fields via config_data_correct_errors in serde_util.rs before
ConfigDataBuilder::build(); remove the `@required` annotations from those four
members in the ConfigData structure so the model no longer treats them as
mandatory and avoids masking missing server fields, or alternatively ensure the
SDK stops auto-filling before build (adjust config_data_correct_errors) so
missing fields are detected.

In `@smithy/models/experiments.smithy`:
- Around line 224-231: The change binds identifier to `@httpQuery` which breaks
the POST /experiments/applicable-variants contract; either revert the
`@httpQuery`("identifier") annotation on the identifier shape so it remains a body
field, or implement dual-support on the server: keep the identifier field in the
request body shape (remove the `@httpQuery`) and update the Rust handler
(experiments handlers.rs) and the deserializer
(shape_applicable_variants_input.rs) to accept identifier from query params
first and fall back to the request body if absent (add tests and a deprecation
log when query-supplied is used) so existing clients remain compatible during
rollout.

---

Outside diff comments:
In
`@clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java`:
- Around line 283-285: In errorCorrection() for GetVersionOutput, the code
currently flips the presence tracker via tracker.setMember($SCHEMA_CONFIG)
without actually populating the config field, leaving config nullable despite
being marked non-nullable; update errorCorrection() to create and assign a
default ConfigData instance (e.g., new ConfigData(...) or
ConfigData.builder().build()) to the config member before calling
tracker.setMember($SCHEMA_CONFIG) so that after errorCorrection().build() the
config field is non-null and will be serialized correctly.

In `@docs/docs/api/Superposition.openapi.json`:
- Around line 2604-2613: The OpenAPI spec marks the POST operation's requestBody
as required but the server handler accepts
Option<Json<ApplicableVariantsRequest>> and treats an absent body as an empty
context; update the operation(s) (the requestBody for the
ApplicableVariantsRequestContent schema) to set "required": false (or remove the
required flag) so generated clients may omit the body when using query-only
calls; search for occurrences of ApplicableVariantsRequestContent and the
requestBody block and make the change consistently (also apply the same fix to
the other occurrence around the ApplicableVariantsRequestContent reference at
the later location).
- Around line 2600-2677: The OpenAPI spec currently documents only the POST for
"/experiments/applicable-variants" (operationId "ApplicableVariants") while the
Rust handlers (crates/experimentation_platform/src/api/experiments/handlers.rs)
register both GET and POST; add a GET operation entry for
"/experiments/applicable-variants" that mirrors the POST (same parameters: query
"identifier", query "prefix" array, headers "x-org-id" and "x-workspace"; same
request/response schemas or if GET is truly query-only, move the request body
into query schema accordingly) and include the same 200 and 500 responses so the
public docs and generated clients expose the GET endpoint used by the
ApplicableVariants handler.

---

Nitpick comments:
In `@clients/python/sdk/superposition_sdk/models.py`:
- Around line 2553-2569: The two functions _serialize_context_map and
_deserialize_context_map duplicate logic already implemented in
_serialize_object and _deserialize_object; remove the duplicate functions,
update any references to call _serialize_object and _deserialize_object instead
(ensure the same parameter ordering and types are preserved when replacing
calls), and run tests to confirm behavior; if any schema/member names differ,
adapt the call by passing the same schema.members["value"] and value dict to
_serialize_object/_deserialize_object so behavior remains identical.
- Around line 2455-2471: In _serialize_object, the inline lambda passed to
m.entry currently closes over the loop variable v which can cause late-binding
issues; change the lambda in function _serialize_object (used with
ShapeSerializer.m.entry) to capture v by value via a default argument (e.g.,
lambda vs, v=v: ...) so each entry writes the correct document value immediately
when invoked.

In `@crates/superposition_provider/src/conversions.rs`:
- Around line 119-145: The Document path (evaluation_context_to_document) is not
normalizing Float values the way evaluation_context_to_value does, causing
inconsistent normalization for NaN/±Inf; update evaluation_context_to_document
so floats are sanitized the same way as evaluation_context_to_value (either
call/evaluate via evaluation_context_to_value then convert that Value to a
Document, or replicate the exact NaN/±Inf normalization logic used in
evaluation_context_to_value) for EvaluationContextFieldValue::Float (and any
numeric Document::Number conversion) so remote and local providers produce
identical normalized representations.

In `@docs/docs/api/Superposition.openapi.json`:
- Around line 6350-6375: Get rid of the duplicated schema by updating the
OpenAPI component that defines GetConfigResponseContent to reference the
existing ConfigData schema instead of repeating its fields; locate the
GetConfigResponseContent component and replace its inline object definition with
a $ref to "#/components/schemas/ConfigData" so both live-config and
versioned-config use the single ConfigData contract (symbols to edit:
ConfigData, GetConfigResponseContent).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b27bc07-444c-48f7-acf1-81777febe411

📥 Commits

Reviewing files that changed from the base of the PR and between 9f610ad and f7d27ba.

📒 Files selected for processing (61)
  • clients/haskell/sdk/Io/Superposition/Model/ApplicableVariantsInput.hs
  • clients/haskell/sdk/Io/Superposition/Model/ConfigData.hs
  • clients/haskell/sdk/Io/Superposition/Model/GetVersionOutput.hs
  • clients/haskell/sdk/Io/Superposition/Model/ListVersionsMember.hs
  • clients/haskell/sdk/SuperpositionSDK.cabal
  • clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/ApplicableVariantsInput.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/Config.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigData.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigVersion.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigOutput.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/GetResolvedConfigWithIdentifierOutput.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/GetVersionOutput.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/ListVersionsMember.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java
  • clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java
  • clients/javascript/sdk/src/commands/ApplicableVariantsCommand.ts
  • clients/javascript/sdk/src/commands/GetVersionCommand.ts
  • clients/javascript/sdk/src/commands/ListVersionsCommand.ts
  • clients/javascript/sdk/src/models/models_0.ts
  • clients/javascript/sdk/src/protocols/Aws_restJson1.ts
  • clients/python/sdk/superposition_sdk/_private/schemas.py
  • clients/python/sdk/superposition_sdk/models.py
  • clients/python/sdk/superposition_sdk/serialize.py
  • crates/experimentation_platform/src/api/experiments/handlers.rs
  • crates/superposition/src/resolve/handlers.rs
  • crates/superposition_provider/examples/local_http_example.rs
  • crates/superposition_provider/src/conversions.rs
  • crates/superposition_provider/src/local_provider.rs
  • crates/superposition_provider/src/remote_provider.rs
  • crates/superposition_provider/src/traits.rs
  • crates/superposition_sdk/src/client/applicable_variants.rs
  • crates/superposition_sdk/src/client/get_version.rs
  • crates/superposition_sdk/src/operation/applicable_variants.rs
  • crates/superposition_sdk/src/operation/applicable_variants/_applicable_variants_input.rs
  • crates/superposition_sdk/src/operation/applicable_variants/builders.rs
  • crates/superposition_sdk/src/operation/get_version/_get_version_output.rs
  • crates/superposition_sdk/src/protocol_serde.rs
  • crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs
  • crates/superposition_sdk/src/protocol_serde/shape_config_data.rs
  • crates/superposition_sdk/src/protocol_serde/shape_get_version.rs
  • crates/superposition_sdk/src/protocol_serde/shape_list_versions_member.rs
  • crates/superposition_sdk/src/serde_util.rs
  • crates/superposition_sdk/src/types.rs
  • crates/superposition_sdk/src/types/_config_data.rs
  • crates/superposition_sdk/src/types/_list_versions_member.rs
  • crates/superposition_sdk/src/types/builders.rs
  • crates/superposition_types/src/api/experiments.rs
  • docs/docs/api/Superposition.openapi.json
  • docs/docs/api/applicable-variants.ParamsDetails.json
  • docs/docs/api/applicable-variants.RequestSchema.json
  • docs/docs/api/applicable-variants.api.mdx
  • docs/docs/api/get-version.StatusCodes.json
  • docs/docs/api/get-version.api.mdx
  • docs/docs/api/list-versions.StatusCodes.json
  • docs/docs/api/list-versions.api.mdx
  • smithy/models/config.smithy
  • smithy/models/experiments.smithy
💤 Files with no reviewable changes (1)
  • crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs

Comment on lines +46 to +50
private ConfigData(Builder builder) {
this.contexts = Collections.unmodifiableList(builder.contexts);
this.overrides = Collections.unmodifiableMap(builder.overrides);
this.defaultConfigs = Collections.unmodifiableMap(builder.defaultConfigs);
this.dimensions = Collections.unmodifiableMap(builder.dimensions);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Copy builder-owned collections before storing them.

Collections.unmodifiableList/Map only protects the returned view. Because the builder keeps caller-owned references, mutating the original collections after build() mutates this supposedly immutable SDK model and can invalidate equals/hashCode; overrides also needs its inner maps copied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/java/sdk/src/main/java/io/juspay/superposition/model/ConfigData.java`
around lines 46 - 50, The constructor in ConfigData currently stores
unmodifiable views of the Builder's collections but does not defensively copy
them, so subsequent mutations of builder.contexts, builder.defaultConfigs,
builder.dimensions, or the maps inside builder.overrides can mutate the supposed
immutable model and break equals()/hashCode(). Fix by making defensive copies
before wrapping: e.g., copy builder.contexts into a new List and wrap with
Collections.unmodifiableList, copy builder.defaultConfigs and builder.dimensions
into new Maps and wrap with Collections.unmodifiableMap, and for overrides
create a new Map whose values are copies of each inner Map (and make those inner
maps unmodifiable) before wrapping the outer map as unmodifiable; update the
ConfigData(Builder builder) assignments for contexts, overrides, defaultConfigs,
and dimensions accordingly (referencing ConfigData constructor, fields
contexts/overrides/defaultConfigs/dimensions and
builder.contexts/builder.overrides).

Comment on lines +5512 to +5525
/**
* deserializeAws_restJson1ConfigData
*/
const de_ConfigData = (
output: any,
context: __SerdeContext
): ConfigData => {
return take(output, {
'contexts': (_: any) => de_ContextList(_, context),
'default_configs': (_: any) => de_Object(_, context),
'dimensions': (_: any) => de_DimensionData(_, context),
'overrides': (_: any) => de_OverridesMap(_, context),
}) as any;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Explicit null config entries are lost during deserialization.

Once version payloads are funneled through de_ConfigData, default_configs, overrides, contexts[*].condition, and dimensions[*].schema all inherit helpers that drop null entries (see Line 5504, Line 5904, and Line 5956). A stored value like default_configs.foo = null will come back as if foo were absent, which changes the payload shape for GetVersion and ListVersions. Use null-preserving deserializers for these document-valued maps before routing version responses through de_ConfigData.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/javascript/sdk/src/protocols/Aws_restJson1.ts` around lines 5512 -
5525, de_ConfigData currently calls deserializers (de_Object, de_OverridesMap,
de_ContextList, de_DimensionData) that drop explicit null map entries, causing
stored nulls (e.g. default_configs.foo = null) to disappear; update
de_ConfigData to use null-preserving deserializers instead: replace the
de_Object call for 'default_configs' with a null-preserving document map
deserializer (e.g. de_ObjectPreserveNull), replace the 'overrides' call with a
null-preserving overrides map deserializer (e.g. de_OverridesMapPreserveNull),
and ensure de_ContextList and de_DimensionData use/compose null-preserving
variants for contexts[*].condition and dimensions[*].schema (or implement
helpers de_ContextListPreserveNull and de_DimensionDataPreserveNull if needed)
so explicit null entries are retained through GetVersion/ListVersions responses.

Comment on lines +983 to +987
let context = if req.method() == actix_web::http::Method::GET {
dimension_params.into_inner()
} else {
req_body.map_or_else(QueryMap::default, |body| body.into_inner().context.into())
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Strip control query params out of the GET evaluation context.

DimensionQuery<QueryMap> reads the full query string, so identifier/toss and prefix now get folded into context on GET requests. That changes cohort evaluation and bucketing whenever the new query parameters are present.

Suggested fix
-    let context = if req.method() == actix_web::http::Method::GET {
-        dimension_params.into_inner()
+    let context = if req.method() == actix_web::http::Method::GET {
+        let mut context = dimension_params.into_inner();
+        context.remove("identifier");
+        context.remove("toss");
+        context.remove("prefix");
+        context
     } else {
         req_body.map_or_else(QueryMap::default, |body| body.into_inner().context.into())
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let context = if req.method() == actix_web::http::Method::GET {
dimension_params.into_inner()
} else {
req_body.map_or_else(QueryMap::default, |body| body.into_inner().context.into())
};
let context = if req.method() == actix_web::http::Method::GET {
let mut context = dimension_params.into_inner();
context.remove("identifier");
context.remove("toss");
context.remove("prefix");
context
} else {
req_body.map_or_else(QueryMap::default, |body| body.into_inner().context.into())
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/experimentation_platform/src/api/experiments/handlers.rs` around lines
983 - 987, The GET branch builds context from DimensionQuery<QueryMap> which
currently folds control params like "identifier", "toss" and "prefix" into the
evaluation context and alters bucketing; change the GET branch so after calling
dimension_params.into_inner() you explicitly remove those control query keys
(e.g. filter out "identifier", "toss", "prefix" from the returned QueryMap)
before assigning to context, keeping the non-GET req_body handling
(req_body.map_or_else(...)) unchanged; reference DimensionQuery, QueryMap, the
context variable, and the req.method() check to locate where to perform the
filtering.

Comment on lines +995 to +996
query_data.identifier,
query_data.prefix.map(|p| p.0),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bound prefix before passing it through.

This list is fully user-controlled and is copied again into a HashSet in get_applicable_variants_helper. Without a length and element-size cap, one request can force large allocations and expensive prefix filtering work.

🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 996-996: Uncontrolled allocation size
This allocation size is derived from a user-provided value and could allocate arbitrary amounts of memory.
This allocation size is derived from a user-provided value and could allocate arbitrary amounts of memory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/experimentation_platform/src/api/experiments/handlers.rs` around lines
995 - 996, The handler is passing query_data.prefix directly into
get_applicable_variants_helper which allows unbounded user-controlled lists to
be copied and turned into a HashSet; instead, validate and bind a sanitized
prefix before passing it: inspect query_data.prefix, enforce a maximum number of
entries and maximum element byte/char length (or reject oversized requests),
truncate elements if desired, and build a new Vec/String collection (e.g., a
sanitized_prefix variable) to pass instead of prefix.map(|p| p.0); ensure this
validation occurs in the same scope that constructs the arguments for
get_applicable_variants_helper and use the sanitized identifier/prefix variables
when calling the helper.

Comment on lines +119 to +130
let (query_data, targeting_key) =
conversions::evaluation_context_to_query_document(context);

let applicable_variants = self
.client
.applicable_variants()
.workspace_id(&self.options.workspace_id)
.org_id(&self.options.org_id)
.set_context(Some(query_data))
.set_identifier(targeting_key)
.set_prefix(prefix_filter)
.send()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle a missing targeting key locally.

The server-side ApplicableVariantsQuery.identifier is required, so forwarding targeting_key through set_identifier(...) when it is None can only fail after a network hop. Short-circuit that case with a clear local outcome instead of surfacing a remote 400 as NetworkError.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/superposition_provider/src/remote_provider.rs` around lines 119 - 130,
Short-circuit when the targeting_key returned by
conversions::evaluation_context_to_query_document(context) is None instead of
calling .set_identifier(None) on the request: check the targeting_key Option
before constructing the self.client.applicable_variants() request, and if it's
None return the appropriate local outcome (e.g. an early Ok result for no
applicable variants or a clear domain-level error) so you never send a request
with set_identifier(None) that would produce a remote 400/NetworkError.

Comment on lines +79 to +91
structure ConfigData {
@required
contexts: ContextList

@required
overrides: OverridesMap

@required
default_configs: Object

@required
dimensions: DimensionData
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t mark these members required if one SDK still auto-fills them.

The new contract says all four ConfigData fields are mandatory, but the Rust SDK currently routes this shape through config_data_correct_errors (crates/superposition_sdk/src/serde_util.rs:970-976), which replaces any missing field with an empty collection before ConfigDataBuilder::build() runs. That turns malformed /version payloads into apparently valid configs and can silently drop server data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@smithy/models/config.smithy` around lines 79 - 91, The schema incorrectly
marks ConfigData members as required (contexts, overrides, default_configs,
dimensions) while the Rust SDK still auto-fills missing fields via
config_data_correct_errors in serde_util.rs before ConfigDataBuilder::build();
remove the `@required` annotations from those four members in the ConfigData
structure so the model no longer treats them as mandatory and avoids masking
missing server fields, or alternatively ensure the SDK stops auto-filling before
build (adjust config_data_correct_errors) so missing fields are detected.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look at the config_data_correct_errors function in serde_util.rs
rg -n "config_data_correct_errors" --type rust -A 15 -B 2

Length of output: 8243


@ayushjain17, here's a plain-language breakdown of what the comment is flagging.


Two things are in conflict

1. The Smithy model says these fields are mandatory

In smithy/models/config.smithy, the new ConfigData structure declares all four fields as @required:

structure ConfigData {
    `@required`
    contexts: ContextList

    `@required`
    overrides: OverridesMap

    `@required`
    default_configs: Object

    `@required`
    dimensions: DimensionData
}

@required means: "the server MUST always send this field — if it's missing, that's an error."


2. The Rust SDK silently fakes missing fields with empty defaults

In crates/superposition_sdk/src/serde_util.rs (lines 970–976), there's a generated helper called config_data_correct_errors:

pub(crate) fn config_data_correct_errors(mut builder: ConfigDataBuilder) -> ConfigDataBuilder {
    if builder.contexts.is_none()        { builder.contexts        = Some(Default::default()) }
    if builder.overrides.is_none()       { builder.overrides       = Some(Default::default()) }
    if builder.default_configs.is_none() { builder.default_configs = Some(Default::default()) }
    if builder.dimensions.is_none()      { builder.dimensions      = Some(Default::default()) }
    builder
}

And this is called during deserialization (shape_config_data.rs:40) before build() runs:

Ok(Some(crate::serde_util::config_data_correct_errors(builder).build()...))

Why this is a problem

Suppose the server returns a /version response where contexts is accidentally missing (a server bug, a schema mismatch, etc.). Instead of failing with a deserialization error (which would alert you to the problem), the SDK silently substitutes an empty list for contexts. Your application then sees ConfigData { contexts: [], ... } and has no idea data was missing.

In short: the model promises strict validation (@required), but the SDK implementation bypasses it — turning potentially serious data loss into a silent no-op.


What the fix options are

Option What to change
Option A – Remove @required from the 4 fields Makes the model honest: these fields are optional in practice, and the SDK's defaulting behaviour is intentional
Option B – Remove config_data_correct_errors auto-filling Makes the SDK enforce the contract: a missing required field on the wire becomes a hard error

The right choice depends on whether the server is guaranteed to always send all four fields. If yes, Option B is safer (fail loudly on bad responses). If the fields can legitimately be absent, Option A (remove @required) is the honest model.

Does that clarify it? Happy to help implement whichever option you choose.


🧠 Learnings used
Learnt from: Datron
Repo: juspay/superposition PR: 773
File: smithy/models/secret.smithy:26-28
Timestamp: 2026-01-14T07:52:22.442Z
Learning: In Smithy resource modeling, model operations that affect all instances of a resource type at a workspace/organizational level (e.g., key rotation for all secrets) as workspace-scoped or standalone workspace-level operations, not bound to individual entity resources like Secret. Only instance-specific operations (create, read, update, delete) should be bound to entity resources. Apply this pattern broadly to Smithy models in the repository, ensuring workspace-scoped actions are defined on Workspace resources or as separate workspace-level operations, while entity resources retain only per-instance operations.

Comment on lines 224 to +231
@required
@notProperty
@httpQuery("identifier")
identifier: String

@notProperty
@httpQuery("prefix")
prefix: StringList
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /repo && pwd && git ls-files | grep -E "\.smithy$" | head -20

Repository: juspay/superposition

Length of output: 119


🏁 Script executed:

cat -n smithy/models/experiments.smithy | sed -n '220,235p'

Repository: juspay/superposition

Length of output: 608


🏁 Script executed:

# Check the broader context of ApplicableVariants operation
rg "ApplicableVariants" -B 5 -A 10 smithy/models/experiments.smithy

Repository: juspay/superposition

Length of output: 1541


🏁 Script executed:

# Search for any backward-compatibility or migration handling in the codebase
rg -i "identifier.*httpQuery|backward.*compat|dual.*support" --type smithy

Repository: juspay/superposition

Length of output: 94


🏁 Script executed:

git log --oneline -n 20 -- smithy/models/experiments.smithy

Repository: juspay/superposition

Length of output: 100


🏁 Script executed:

# Get the previous version of the Smithy file to compare
git show HEAD~1:smithy/models/experiments.smithy | grep -A 20 "structure ApplicableVariantsInput"

Repository: juspay/superposition

Length of output: 100


🏁 Script executed:

# Search for the server implementation of ApplicableVariants
fd -e java -e go -e ts -e js | xargs grep -l "ApplicableVariants" | head -10

Repository: juspay/superposition

Length of output: 6795


🏁 Script executed:

cat -n clients/java/sdk/src/main/java/io/juspay/superposition/model/ApplicableVariantsInput.java

Repository: juspay/superposition

Length of output: 13680


🏁 Script executed:

# Find the server-side handler for ApplicableVariants
fd -type f \( -name "*.java" -o -name "*.go" -o -name "*.ts" -o -name "*.kt" \) | xargs grep -l "applicable-variants" | grep -v client | head -10

Repository: juspay/superposition

Length of output: 234


🏁 Script executed:

# Search for server code in common directories
find . -path ./clients -prune -o -type f \( -name "*.java" -o -name "*.kt" \) -print | xargs grep -l "ApplicableVariants" | grep -v client

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

find . -type f -name "*.java" -o -name "*.kt" | grep -v clients | head -30

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Look for server code directories
find . -type d -name "src" -o -name "server" -o -name "backend" | head -10

Repository: juspay/superposition

Length of output: 391


🏁 Script executed:

# Search for any handler or controller code
find . -type f \( -name "*Handler*.java" -o -name "*Controller*.java" -o -name "*Service*.java" \) | grep -v clients | head -20

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Check for README or documentation that might explain the API contract
find . -type f \( -name "README*" -o -name "CHANGELOG*" -o -name "MIGRATION*" \) | head -10

Repository: juspay/superposition

Length of output: 440


🏁 Script executed:

# Look for any test files that might show the expected request format
find . -type f -name "*.test.*" -o -name "*Test*" | grep -i applicable | head -10

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Check git log more broadly to understand repo state
git log --all --oneline | head -20

Repository: juspay/superposition

Length of output: 1241


🏁 Script executed:

# Check what changed in the ApplicableVariants structure in recent commits
git log --oneline -- smithy/models/experiments.smithy | head -20

Repository: juspay/superposition

Length of output: 100


🏁 Script executed:

# Show the diff for the most recent changes to the Smithy file
git show 330279e7:smithy/models/experiments.smithy | grep -A 20 "structure ApplicableVariantsInput"

Repository: juspay/superposition

Length of output: 916


🏁 Script executed:

# Check the commit that says "Spec for list exp and get config endpoints"
git show 330279e7 -- smithy/models/experiments.smithy | head -100

Repository: juspay/superposition

Length of output: 2558


🏁 Script executed:

# Get the full diff for ApplicableVariants in that commit
git show 330279e7 -- smithy/models/experiments.smithy | grep -A 30 "ApplicableVariantsInput"

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Check the current state and confirm the changes
git show HEAD:smithy/models/experiments.smithy | grep -A 15 "structure ApplicableVariantsInput"

Repository: juspay/superposition

Length of output: 496


🏁 Script executed:

# Check if there's any migration handling or dual-support in the codebase
rg -i "identifier.*body|backward|compat|migration" --type-list | head -20

Repository: juspay/superposition

Length of output: 453


🏁 Script executed:

# Get the precise diff for ApplicableVariantsInput
git show 330279e7 -- smithy/models/experiments.smithy | grep -B 5 -A 20 "@httpQuery(\"identifier\")"

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Search for any body parameter support or migration logic
rg "identifier" smithy/models/experiments.smithy -B 3 -A 3

Repository: juspay/superposition

Length of output: 334


🏁 Script executed:

# Check if there are any server/backend implementations that might handle both formats
find . -type f \( -name "*.rs" -o -name "*.go" \) | head -20

Repository: juspay/superposition

Length of output: 881


🏁 Script executed:

# Search for ApplicableVariants in the Rust server code
find ./crates -type f -name "*.rs" | xargs grep -l "applicable.*variants" -i

Repository: juspay/superposition

Length of output: 1514


🏁 Script executed:

# Check the experiments API handler
find ./crates -type f -name "*.rs" | xargs grep -l "Experiments\|experiments" | grep -E "handler|api" | head -10

Repository: juspay/superposition

Length of output: 633


🏁 Script executed:

# Look at the experiments service/handler structure
ls -la ./crates/*/src/api/

Repository: juspay/superposition

Length of output: 3075


🏁 Script executed:

# Check the experiments handler for ApplicableVariants
cat -n ./crates/experimentation_platform/src/api/experiments/handlers.rs

Repository: juspay/superposition

Length of output: 50376


🏁 Script executed:

# Check the applicable_variants operation input parsing
cat -n ./crates/superposition_sdk/src/operation/applicable_variants/_applicable_variants_input.rs

Repository: juspay/superposition

Length of output: 9397


🏁 Script executed:

# Check the protocol serde for applicable variants
cat -n ./crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs

Repository: juspay/superposition

Length of output: 934


🏁 Script executed:

# Confirm the exact change made in commit 330279e7
git show 330279e7:smithy/models/experiments.smithy | grep -A 10 "structure ApplicableVariantsInput"

Repository: juspay/superposition

Length of output: 401


🏁 Script executed:

# Check if there's any request body parameter that still accepts identifier
rg "identifier.*body|body.*identifier" crates/

Repository: juspay/superposition

Length of output: 3579


Binding identifier to @httpQuery creates a breaking wire change for the POST /experiments/applicable-variants operation.

Moving identifier from the request body to a query parameter changes the request contract for all existing callers. Without server-side dual-support during rollout, older SDKs sending identifier in the body will fail immediately upon deployment when the server stops reading from that location.

Evidence from codebase

Commit 330279e introduced the change: identifier was previously a plain body field, now has @httpQuery("identifier") annotation. The Rust server handler (line 995 of crates/experimentation_platform/src/api/experiments/handlers.rs) extracts identifier from query parameters only, and the serialization layer (shape_applicable_variants_input.rs) does not include identifier in the request body. No fallback parsing logic exists to accept identifier from both locations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@smithy/models/experiments.smithy` around lines 224 - 231, The change binds
identifier to `@httpQuery` which breaks the POST /experiments/applicable-variants
contract; either revert the `@httpQuery`("identifier") annotation on the
identifier shape so it remains a body field, or implement dual-support on the
server: keep the identifier field in the request body shape (remove the
`@httpQuery`) and update the Rust handler (experiments handlers.rs) and the
deserializer (shape_applicable_variants_input.rs) to accept identifier from
query params first and fall back to the request body if absent (add tests and a
deprecation log when query-supplied is used) so existing clients remain
compatible during rollout.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server updated, please check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the handler for ApplicableVariants - how identifier is extracted
grep -n "identifier\|query_data\|ApplicableVariantsQuery\|ApplicableVariantsRequest\|get_applicable_variants_helper\|get_applicable_variants_handler" crates/experimentation_platform/src/api/experiments/handlers.rs | head -60

echo "---"
# Check the ApplicableVariantsQuery and ApplicableVariantsRequest structs
cat crates/superposition_types/src/api/experiments.rs | grep -A 20 "ApplicableVariants"

echo "---"
# Check the Rust SDK operation input
grep -n "identifier\|prefix\|uri_query\|httpQuery" crates/superposition_sdk/src/operation/applicable_variants/_applicable_variants_input.rs

echo "---"
# Check the serialization layer
cat crates/superposition_sdk/src/protocol_serde/shape_applicable_variants_input.rs

echo "---"
# Check the uri_query in applicable_variants operation
grep -n "identifier\|prefix\|uri_query" crates/superposition_sdk/src/operation/applicable_variants.rs 2>/dev/null || \
grep -rn "identifier\|prefix\|uri_query" crates/superposition_sdk/src/operation/applicable_variants/ | grep -v "_input\|builders\|_output"

Length of output: 5277


@ayushjain17, thanks for the update! I've reviewed the current server-side code. Here's the assessment:

✅ What's fixed:

  • The handler (handlers.rs lines 983–988) now correctly reads identifier from query parameters (query_data.identifier) for both GET and POST methods.
  • The Rust SDK's uri_query properly serializes both identifier and prefix as query params.
  • shape_applicable_variants_input.rs no longer emits identifier in the request body — only context is sent in the body.

⚠️ Residual concern — no actual fallback for old clients:

ApplicableVariantsRequest still has identifier in the body with a TODO comment:

// TODO: remove this once services start using newer sdk
#[serde(alias = "toss", deserialize_with = "deserialize_identifier")]
pub identifier: String,

However, the handler never reads req_body.identifier — it only uses query_data.identifier. Since ApplicableVariantsQuery.identifier is a required String (not Option<String>), Actix's Query<> extractor will return a 400 error before the handler even runs if an old client omits identifier from the query string. The body field is dead code.

If coordinated migration is the intent (i.e., all callers will be updated simultaneously), the current server state is fine. But if true backward compatibility is needed during rollout, the fix would be to make query_data.identifier optional and fall back to req_body.identifier:

// In ApplicableVariantsQuery
pub identifier: Option<String>,

// In handler
let identifier = query_data.identifier
    .or_else(|| req_body.as_ref().and_then(|b| b.identifier.clone()))
    .ok_or_else(|| bad_argument!("identifier is required"))?;

Is this an intentional coordinated cutover, or does backward compatibility need to be maintained?


🧠 Learnings used
Learnt from: Datron
Repo: juspay/superposition PR: 773
File: smithy/models/secret.smithy:26-28
Timestamp: 2026-01-14T07:52:22.442Z
Learning: In Smithy resource modeling, model operations that affect all instances of a resource type at a workspace/organizational level (e.g., key rotation for all secrets) as workspace-scoped or standalone workspace-level operations, not bound to individual entity resources like Secret. Only instance-specific operations (create, read, update, delete) should be bound to entity resources. Apply this pattern broadly to Smithy models in the repository, ensuring workspace-scoped actions are defined on Workspace resources or as separate workspace-level operations, while entity resources retain only per-instance operations.

@ayushjain17 ayushjain17 force-pushed the fix/remote-provider branch from abca9bc to c00cfad Compare March 26, 2026 11:42
Comment thread crates/experimentation_platform/src/api/experiments/handlers.rs Fixed
mut context: EvaluationContext,
prefix_filter: Option<&[String]>,
prefix_filter: Option<Vec<String>>,
) -> Result<Map<String, Value>> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a readiness check here :

    async fn ensure_ready(&self) -> Result<()> {
        let status = self.status.read().await;
        match *status {
            ProviderStatus::Ready => Ok(()),
            ProviderStatus::NotReady => Err(SuperpositionError::ProviderError(
                "Provider not initialized. Call initialize() before evaluating flags.".into(),
            )),
            ProviderStatus::Error => Err(SuperpositionError::ProviderError(
                "Provider is in error state.".into(),
            )),
            ProviderStatus::STALE => {
                log::warn!("SuperpositionAPIProvider: provider data is stale, proceeding with request");
                Ok(())
            }
        }
    }

We can then do : ensure_ready().await? here
We are doing something similar in localProvider , we don't need fresh_data since caching isn't there yet but
adding readiness check would be useful here

@ayushjain17 ayushjain17 force-pushed the fix/remote-provider branch from c00cfad to 2f25f83 Compare March 26, 2026 12:49
Comment on lines +53 to +57
let global_context = self.global_context.read().await;
context.merge_missing(&global_context);

// 3. Extract context and targeting_key
let (query_data, _targeting_key) =
conversions::evaluation_context_to_query(context);
let (query_data, targeting_key) =
conversions::evaluation_context_to_query_document(context);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same steps being repeated in get_applicable_variants

@ayushjain17 ayushjain17 force-pushed the fix/remote-provider branch 2 times, most recently from 7819098 to 307e1b8 Compare March 27, 2026 12:17
@ayushjain17 ayushjain17 force-pushed the fix/remote-provider branch from 307e1b8 to 57cea65 Compare March 27, 2026 12:45
@ayushjain17 ayushjain17 added this pull request to the merge queue Mar 27, 2026
Merged via the queue into main with commit 6d09519 Mar 27, 2026
37 of 39 checks passed
@ayushjain17 ayushjain17 deleted the fix/remote-provider branch March 27, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants