Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
d8ba710
feat: add toml parsing to superposition_core
knutties Dec 21, 2025
9e97fbd
feat: linting fixes
knutties Dec 22, 2025
3ae2d19
feat: binding support for js/java/python
knutties Jan 1, 2026
658fd98
feat: binding support for haskell
knutties Jan 2, 2026
f4424e2
feat: unify local testing and CI packaging of libraries
knutties Jan 2, 2026
fc1511f
docs: add TOML response format design for get_config API
knutties Jan 2, 2026
501a679
refactor: rename TomlParseError to TomlError and add serialization va…
knutties Jan 2, 2026
4015a53
feat: add TOML serialization helper functions
knutties Jan 2, 2026
8011db3
feat: implement serialize_to_toml function
knutties Jan 2, 2026
71785aa
feat: add TOML response support to get_config endpoint
knutties Jan 2, 2026
851ad16
feat: add TOML response support to get_config endpoint
knutties Jan 2, 2026
6b50bf5
test: add comprehensive serialization tests
knutties Jan 2, 2026
c9947f0
docs: add API documentation and update design document
knutties Jan 2, 2026
914f48f
fix: serialize context overrides correctly
knutties Jan 2, 2026
2fa5d17
fix: use correct override key for context serialization
knutties Jan 2, 2026
ee7c441
feat: add dimension type support to TOML parsing and serialization
knutties Jan 2, 2026
b1ac61e
refactor: encode local_cohort dimension in type field
knutties Jan 2, 2026
fe00dfb
refactor: add shared JSON schema validation in superposition_core
knutties Jan 2, 2026
6153889
feat: add remote_cohort dimension type support to TOML parser
knutties Jan 2, 2026
0b3ebb5
feat: remove TOML_BINDINGS_SUMMARY.md
knutties Jan 2, 2026
6617fbc
style: use inline format for dimensions in all test examples
knutties Jan 2, 2026
ec98120
ci: fix example build breakage
knutties Jan 2, 2026
bfb2bb5
ci: fix crate name for example
knutties Jan 2, 2026
f7f4dc5
ci: run local build to generate Cargo.lock
knutties Jan 2, 2026
09b4d4d
fix: free FFI-allocated res pointer in parseTomlConfig
knutties Jan 3, 2026
47dcfb6
fix: follow established pattern for freeing native strings in parseTo…
knutties Jan 3, 2026
920898c
docs: fix TOML example in ffi_parse_toml_config doc comment
knutties Jan 3, 2026
f306004
ci: move blake3 to workspace dependency
knutties Jan 3, 2026
6f5efb7
refactor: separate schema compilation from validation
knutties Jan 3, 2026
2193f13
ci: fix deprecation and uniff regen
knutties Jan 4, 2026
e843c28
ci: fix duplicate else path
knutties Jan 4, 2026
dfcb4aa
ci: fix tests and remove unwanted validations
knutties Jan 4, 2026
fcf1f32
ci: add libpq for nix builds of superposition_toml_example
knutties Jan 4, 2026
5b19f46
test: disable toml_parser unit tests when jsonlogic feature is enabled
knutties Jan 4, 2026
5eedd65
chore: remove unused imports
knutties Jan 4, 2026
903a701
chore: exclude toml tests for jsonlogic flow
knutties Jan 4, 2026
5229878
ci: adding bindings-test execution in CI
knutties Jan 5, 2026
ee2ab8c
refactor: move validation_err_to_str from service_utils to superposit…
knutties Jan 5, 2026
f495155
ci: npm install before building/running tests
knutties Jan 5, 2026
145ffee
fix: address review comments
knutties Jan 6, 2026
4d847ea
fix: move priority computation to superposition_core
knutties Jan 6, 2026
0a6a5f2
refactor: remove calculate_priority_from_index, use calculate_weight_…
knutties Jan 6, 2026
5e79d43
feat: URL-encode dimension keys and values in TOML contexts
knutties Jan 7, 2026
f603bdc
feat: quote default-config and override keys with special characters
knutties Jan 7, 2026
8745c15
feat: serialize object values as triple-quoted JSON strings
knutties Jan 7, 2026
e47c2e0
feat: switch to toml representation for objects
knutties Jan 20, 2026
97b92af
feat: address review comments - round 1
knutties Jan 20, 2026
75ce84a
fix: broken tests
knutties Jan 20, 2026
a1efce2
feat: introduce intermediate type
knutties Jan 21, 2026
ac96c5f
feat: introduce intermediate type
knutties Jan 21, 2026
0fc81f2
feat: add dedicated /config/toml endpoint with schema from database
knutties Jan 21, 2026
20eb52e
fix: simplify core_parse_toml_config to return Config JSON directly
knutties Jan 22, 2026
c993eac
fix: address review comments
knutties Jan 27, 2026
c264a42
ci: fix compilation error
knutties Jan 31, 2026
466ae7e
fix: fix broken tests
knutties Jan 31, 2026
3f0b7f6
ci: fix compilation/tests
knutties Feb 1, 2026
0eeab8d
build: fix bindings test run
knutties Feb 2, 2026
25da504
build: install haskell for bindings-test
knutties Feb 2, 2026
06569f5
build: install libgmp-dev for haskell-bindings-test
knutties Feb 2, 2026
3d70706
fix: haskell changes fix
knutties Feb 2, 2026
5883f45
fix: compute dependency graph
knutties Feb 3, 2026
d5671e0
fix: fix dependency graph computation
knutties Feb 3, 2026
9782275
fix: add test for local_cohorts
knutties Feb 4, 2026
bc8ba16
fix: unused import and wrong var usage
knutties Feb 4, 2026
50833c5
feat: use pure toml for contexts
knutties Feb 5, 2026
19841f1
feat: fix bindings-test
knutties Feb 5, 2026
5d9b199
feat: remove design-docs folder
knutties Feb 5, 2026
5ef51f7
fix: commit ayush feedback changes
knutties Feb 9, 2026
cd07076
fix: use native toml types
knutties Feb 10, 2026
b719275
fix: more toml nativity
knutties Feb 10, 2026
19d6d45
fix: checkpoint toml members
knutties Feb 12, 2026
579e1ce
fix: cleaned up toml implementation
knutties Feb 12, 2026
f0e307c
fix: move non-toml stuff to outer helpers/validations
knutties Feb 12, 2026
4d9fd3c
fix: fixes and review comments
knutties Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/ci_check_pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Install postgres libs
run: |
sudo apt update
sudo apt-get -y install postgresql libpq-dev
sudo apt-get -y install postgresql libpq-dev libgmp-dev
- name: Install Bun
uses: oven-sh/setup-bun@v2
Expand All @@ -155,6 +155,24 @@ jobs:
shell: bash
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

- name: Setup Haskell environment (GHC and Cabal)
uses: haskell-actions/setup@v2
with:
ghc-version: '9.6.7' # Specify the GHC version (e.g., '9.2' or 'latest')
cabal-version: '3.16' # Specify the Cabal version (e.g., '3.8' or 'latest')

- name: Update Cabal package database
run: cabal update

- name: run tests
shell: bash
run: |
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ backend/.env
/result
/result-*
clients/haskell/result
clients/haskell/dist-newstyle
dist-newstyle
# dev
bacon.toml
docker-compose/localstack/export_cyphers.sh
Expand Down Expand Up @@ -68,3 +68,8 @@ clients/generated/smithy/typescript/tsconfig.*

.npmrc
.zed

# Dynamic libraries copied for testing
*.dylib
*.so
*.dll
Comment on lines +73 to +75
Copy link
Collaborator

Choose a reason for hiding this comment

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

For testing, should we link to libraries in target dir?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@Datron - yes, we are linking to libraries in target dir for the bindings-test make target - are you saying we should or we should not?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@knutties I assumed we were copying the libs from target to root dir for testing since the comment

# Dynamic libraries copied for testing

If we are linking with binaries in target dir then we can remove this gitignore. Though this is a nitpick and need not be done

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think for python it copies the library instead of using the target path - so is useful for now. Need to see if the Python code can be changed to pick from the target itself.

43 changes: 32 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ members = [
"examples/experimentation_client_integration_example",
"examples/cac_client_integration_example",
"examples/superposition-demo-app",
"examples/superposition_toml_example",
]

[[workspace.metadata.leptos]]
Expand All @@ -45,6 +46,7 @@ anyhow = "1.0.75"
aws-sdk-kms = { version = "1.38.0" }
base64 = "0.21.2"
bigdecimal = { version = "0.3.1", features = ["serde"] }
blake3 = "1.3.3"
cfg-if = "1.0.0"
chrono = { version = "0.4.26", features = ["serde"] }
derive_more = "^0.99"
Expand Down
60 changes: 55 additions & 5 deletions clients/haskell/superposition-bindings/lib/FFI/Superposition.hs
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}

module FFI.Superposition (getResolvedConfig, ResolveConfigParams (..), defaultResolveParams, MergeStrategy (..)) where
module FFI.Superposition (getResolvedConfig, ResolveConfigParams (..), defaultResolveParams, MergeStrategy (..), parseTomlConfig) where

import Control.Monad (when)
import Data.Aeson (Value, eitherDecodeStrict')
import Data.ByteString (packCString)
import Data.Either (fromRight)
import Data.Foldable (traverse_)
import Data.Maybe (fromMaybe)
import Data.Text (unpack)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8')
import Foreign (callocBytes, nullPtr)
import Foreign.C.String (CString, newCString, peekCAString)
import Foreign.ForeignPtr (newForeignPtr, withForeignPtr)
import Foreign.Ptr (FunPtr)
import Foreign.Marshal (free)
import Prelude

type BufferSize = Int
errorBufferSize :: BufferSize
errorBufferSize = 2048

foreign import capi "superposition_core.h core_get_resolved_config"
get_resolved_config ::
Expand All @@ -20,7 +30,7 @@ foreign import capi "superposition_core.h core_get_resolved_config"
CString ->
-- | overrides_json
CString ->
-- | dimenson_info_json
-- | dimension_info_json
CString ->
-- | query_data_json
CString ->
Expand All @@ -35,6 +45,18 @@ foreign import capi "superposition_core.h core_get_resolved_config"
-- | resolved config json
IO CString

foreign import capi "superposition_core.h core_parse_toml_config"
parse_toml_config ::
-- | toml_content
CString ->
-- | error-buffer
CString ->
-- | parsed config json
IO CString

foreign import capi "superposition_core.h &core_free_string"
p_free_string :: FunPtr (CString -> IO ())

data MergeStrategy = Merge | Replace

instance Show MergeStrategy where
Expand Down Expand Up @@ -67,7 +89,7 @@ defaultResolveParams =

getResolvedConfig :: ResolveConfigParams -> IO (Either String String)
getResolvedConfig params = do
ebuf <- callocBytes 256
ebuf <- callocBytes errorBufferSize
let ResolveConfigParams {..} = params
newOrNull = maybe (pure nullPtr) newCString
freeNonNull p = when (p /= nullPtr) (free p)
Expand All @@ -94,8 +116,36 @@ getResolvedConfig params = do
exp
ebuf
err <- peekCAString ebuf
traverse_ freeNonNull [dc, ctx, ovrs, qry, mergeS, pfltr, exp, ebuf]
traverse_ freeNonNull [dc, ctx, ovrs, di, qry, mergeS, pfltr, exp, ebuf]
pure $ case (res, err) of
(Just cfg, []) -> Right cfg
(Nothing, []) -> Left "null pointer returned"
_ -> Left err

-- | Parse TOML configuration string into structured format
-- Returns JSON matching the Config type with:
-- - contexts: array of context objects with id, condition, priority, weight, override_with_keys
-- - overrides: object mapping override IDs to override key-value pairs
-- - default_configs: object with configuration key-value pairs
-- - dimensions: object mapping dimension names to dimension info (schema, position, etc.)
parseTomlConfig :: String -> IO (Either String Value)
parseTomlConfig tomlContent = do
ebuf <- callocBytes errorBufferSize
tomlStr <- newCString tomlContent
res <- parse_toml_config tomlStr ebuf
errBytes <- packCString ebuf
let errText = fromRight mempty $ decodeUtf8' errBytes
result <- if res /= nullPtr
then do
resFptr <- newForeignPtr p_free_string res
-- Registers p_free_string as the finalizer (for automatic cleanup)
withForeignPtr resFptr $ fmap Just . packCString
else pure Nothing
free tomlStr
free ebuf
pure $ case (result, errText) of
(Just cfg, t) | T.null t -> case eitherDecodeStrict' cfg of
Right val -> Right val
Left e -> Left $ "JSON parse error: " ++ e
(Nothing, t) | T.null t -> Left "null pointer returned"
_ -> Left (unpack errText)
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ library
exposed-modules: FFI.Superposition
-- other-modules:
-- other-extensions:
build-depends: base ^>=4.18.2.0
build-depends: base >=4.17 && <5,
aeson,
bytestring,
text
hs-source-dirs: lib
default-language: GHC2021
default-extensions: CApiFFI
extra-libraries: superposition_core
include-dirs: ../../../target/include
Copy link
Contributor

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:

#!/bin/bash
# Check for README files and build documentation in haskell clients directory
find clients/haskell -type f -name "README*" -o -name "*.md" | head -20

Repository: juspay/superposition

Length of output: 397


🏁 Script executed:

#!/bin/bash
# Check for Makefile, build scripts in haskell clients directory
find clients/haskell -type f \( -name "Makefile" -o -name "build.sh" -o -name "build.py" -o -name "*.sh" \) | head -20

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Search for any documentation about Rust in the haskell clients directory
find clients/haskell -type f \( -name "*.md" -o -name "*.txt" -o -name "README*" \) -exec cat {} \; 2>/dev/null | head -50

Repository: juspay/superposition

Length of output: 1682


🏁 Script executed:

#!/bin/bash
# Examine the cabal file itself and nearby structure
cat -n clients/haskell/superposition-bindings/superposition-bindings.cabal | head -60

Repository: juspay/superposition

Length of output: 1528


🏁 Script executed:

#!/bin/bash
# Check the directory structure around the Haskell bindings
ls -la clients/haskell/
ls -la clients/haskell/superposition-bindings/

Repository: juspay/superposition

Length of output: 1271


🏁 Script executed:

#!/bin/bash
# Check cabal.project file
cat clients/haskell/cabal.project

Repository: juspay/superposition

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Check default.nix file for Rust build handling
cat clients/haskell/default.nix

Repository: juspay/superposition

Length of output: 1216


🏁 Script executed:

#!/bin/bash
# Look for GitHub Actions or CI/CD workflows
find . -type d -name ".github" -o -name ".gitlab-ci.yml" 2>/dev/null | head -5

Repository: juspay/superposition

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Check if there's a top-level README or build documentation
cat README.md 2>/dev/null | head -100

Repository: juspay/superposition

Length of output: 7289


🏁 Script executed:

#!/bin/bash
# Check for any setup.hs that might handle the build order
cat clients/haskell/superposition-bindings/Setup.hs

Repository: juspay/superposition

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Look for any Nix flake or build files at root
ls -la | grep -E "flake|nix|Makefile|setup"

Repository: juspay/superposition

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Search for GitHub Actions workflows that build Haskell code
find .github/workflows -name "*.yaml" -o -name "*.yml" 2>/dev/null | head -10

Repository: juspay/superposition

Length of output: 193


🏁 Script executed:

#!/bin/bash
# Check for any workflow that mentions haskell or cabal
find .github -type f \( -name "*.yaml" -o -name "*.yml" \) -exec grep -l "haskell\|cabal\|superposition-bindings" {} \;

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Look at flake.nix to understand the complete build system
cat flake.nix | head -100

Repository: juspay/superposition

Length of output: 2633


🏁 Script executed:

#!/bin/bash
# Check the CI workflow files to see how Haskell is built
cat .github/workflows/ci_check_pr.yaml

Repository: juspay/superposition

Length of output: 15520


🏁 Script executed:

#!/bin/bash
# Check the release workflow to see how it handles Haskell builds
cat .github/workflows/release.yaml | head -150

Repository: juspay/superposition

Length of output: 5746


🏁 Script executed:

#!/bin/bash
# Check nix workflow
cat .github/workflows/nix.yaml

Repository: juspay/superposition

Length of output: 449


🏁 Script executed:

#!/bin/bash
# Look for any documentation about building in clients/haskell
find clients/haskell -name "*.md" -exec cat {} \;

Repository: juspay/superposition

Length of output: 4822


The hardcoded include path requires clarification for non-Nix builds.

The include-dirs: ../../../target/include on line 29 points to a relative path outside the package. While the Nix build system (used in CI and dev shells) properly manages the superposition_core package dependency and provides the correct C_INCLUDE_PATH environment variable, the hardcoded relative path is fragile and unclear.

Consider:

  • Verifying whether this path is necessary when building within Nix (nix develop) or if the environment variable C_INCLUDE_PATH from default.nix is sufficient
  • If the path is needed for non-Nix builds, documenting the Rust compilation prerequisite and directory structure assumption
  • Alternatively, using a Setup.hs script or environment variable to avoid hardcoding the path
🤖 Prompt for AI Agents
In clients/haskell/superposition-bindings/superposition-bindings.cabal around
line 29, the hardcoded include-dirs: ../../../target/include is fragile for
non-Nix builds; remove or conditionalize the hardcoded relative path and instead
rely on the C_INCLUDE_PATH provided by the Nix environment when building under
Nix, and for non-Nix builds either document the required Rust build output
location and prerequisite steps in the package README or implement a Setup.hs
that reads an environment variable (e.g., SUPERPOSITION_CORE_INCLUDE) to set
include-dirs at configure time so the path is not hardcoded.


test-suite superposition-bindings-test
import: warnings
Expand All @@ -36,7 +40,9 @@ test-suite superposition-bindings-test
hs-source-dirs: test
main-is: Main.hs
build-depends:
base ^>=4.18.2.0,
base >=4.17 && <5,
HUnit,
async,
aeson,
bytestring,
superposition-bindings
Loading
Loading