|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +flagd-evaluator is a **WebAssembly-based JSON Logic evaluator** designed for feature flag evaluation. It's written in Rust and compiled to WASM (~2.4MB optimized binary) to provide consistent evaluation logic across all OpenFeature flagd providers (Java, JavaScript, .NET, Go, Python, PHP, etc.). |
| 8 | + |
| 9 | +**Key Purpose**: This is the shared evaluation engine used by all in-process flagd providers to ensure uniform behavior across polyglot architectures. See the [flagd providers specification](https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md) for integration details. |
| 10 | + |
| 11 | +## Essential Commands |
| 12 | + |
| 13 | +### Development |
| 14 | + |
| 15 | +```bash |
| 16 | +# Build for native development/testing |
| 17 | +cargo build |
| 18 | + |
| 19 | +# Build optimized WASM (production) |
| 20 | +cargo build --target wasm32-unknown-unknown --no-default-features --release --lib |
| 21 | + |
| 22 | +# WASM output location |
| 23 | +# target/wasm32-unknown-unknown/release/flagd_evaluator.wasm |
| 24 | + |
| 25 | +# Build CLI tool |
| 26 | +cargo build --release --bin flagd-eval |
| 27 | +``` |
| 28 | + |
| 29 | +### Testing |
| 30 | + |
| 31 | +```bash |
| 32 | +# Run all tests |
| 33 | +cargo test |
| 34 | + |
| 35 | +# Run specific test file |
| 36 | +cargo test --test integration_tests |
| 37 | +cargo test --test cli_tests |
| 38 | + |
| 39 | +# Run specific test by name |
| 40 | +cargo test test_fractional_operator |
| 41 | + |
| 42 | +# Run with output visible |
| 43 | +cargo test -- --nocapture |
| 44 | +``` |
| 45 | + |
| 46 | +### Code Quality |
| 47 | + |
| 48 | +```bash |
| 49 | +# Format code (required before commit) |
| 50 | +cargo fmt |
| 51 | + |
| 52 | +# Check formatting without changing files |
| 53 | +cargo fmt -- --check |
| 54 | + |
| 55 | +# Lint code (must pass with no warnings) |
| 56 | +cargo clippy -- -D warnings |
| 57 | +``` |
| 58 | + |
| 59 | +### CLI Tool Usage |
| 60 | + |
| 61 | +```bash |
| 62 | +# Evaluate a JSON Logic rule |
| 63 | +cargo run --bin flagd-eval -- eval --rule '{"==": [1, 1]}' --data '{}' |
| 64 | + |
| 65 | +# Load rule/data from file (use @ prefix) |
| 66 | +cargo run --bin flagd-eval -- eval --rule @examples/rules/basic.json --data '{"age": 25}' |
| 67 | + |
| 68 | +# Run test suite |
| 69 | +cargo run --bin flagd-eval -- test examples/rules/test-suite.json |
| 70 | + |
| 71 | +# List available operators |
| 72 | +cargo run --bin flagd-eval -- operators |
| 73 | +``` |
| 74 | + |
| 75 | +## Architecture |
| 76 | + |
| 77 | +### Core Design Principles |
| 78 | + |
| 79 | +1. **WASM-First**: Compiled to WebAssembly for cross-language portability |
| 80 | +2. **No External Dependencies**: Single WASM file, no JNI, no JavaScript bindings |
| 81 | +3. **Chicory Compatible**: Works with pure Java WASM runtimes (no native code) |
| 82 | +4. **Memory Safe**: Explicit alloc/dealloc, no panics, all errors returned as JSON |
| 83 | +5. **Size Optimized**: Aggressive compilation flags (`opt-level = "z"`, LTO, panic = "abort") |
| 84 | + |
| 85 | +### Module Organization |
| 86 | + |
| 87 | +``` |
| 88 | +src/ |
| 89 | +├── lib.rs # Main entry point, WASM exports (evaluate_logic, update_state, evaluate) |
| 90 | +├── evaluation.rs # Core flag evaluation logic, context enrichment ($flagd properties) |
| 91 | +├── memory.rs # WASM memory management (alloc/dealloc, pointer packing) |
| 92 | +├── storage/ # Thread-local flag state storage |
| 93 | +├── operators/ # Custom JSON Logic operators |
| 94 | +│ ├── fractional.rs # MurmurHash3-based consistent bucketing for A/B testing |
| 95 | +│ ├── sem_ver.rs # Semantic version comparison (=, !=, <, <=, >, >=, ^, ~) |
| 96 | +│ ├── starts_with.rs # String prefix matching |
| 97 | +│ └── ends_with.rs # String suffix matching |
| 98 | +├── model/ # Flag configuration data structures |
| 99 | +├── validation.rs # JSON Schema validation against flagd schemas |
| 100 | +└── bin/ |
| 101 | + └── flagd-eval.rs # CLI tool for testing rules without WASM |
| 102 | +``` |
| 103 | + |
| 104 | +### Key Architectural Concepts |
| 105 | + |
| 106 | +**WASM Exports** (lib.rs:138-625): |
| 107 | +- `evaluate_logic(rule_ptr, rule_len, data_ptr, data_len) -> u64` - Direct JSON Logic evaluation |
| 108 | +- `update_state(config_ptr, config_len) -> u64` - Store flag configuration, returns changed flags |
| 109 | +- `evaluate(flag_key_ptr, flag_key_len, context_ptr, context_len) -> u64` - Evaluate stored flag |
| 110 | +- `alloc(len) -> *mut u8` - Allocate WASM memory |
| 111 | +- `dealloc(ptr, len)` - Free WASM memory |
| 112 | +- `set_validation_mode(mode) -> u64` - Set strict (0) or permissive (1) validation |
| 113 | + |
| 114 | +All functions return **packed u64**: upper 32 bits = pointer, lower 32 bits = length. |
| 115 | + |
| 116 | +**Memory Model**: Caller allocates input buffers, callee allocates result buffers. Caller must free all allocations. UTF-8 JSON strings for all inputs/outputs. |
| 117 | + |
| 118 | +**Context Enrichment** (evaluation.rs): The evaluator automatically injects standard `$flagd` properties into evaluation context: |
| 119 | +- `$flagd.flagKey` - The flag being evaluated |
| 120 | +- `$flagd.timestamp` - Unix timestamp (seconds) at evaluation time |
| 121 | +- `targetingKey` - Defaults to empty string if not provided in context |
| 122 | + |
| 123 | +**Custom Operators**: All registered via `datalogic_rs::Operator` trait (operators/mod.rs). See [flagd custom operations spec](https://flagd.dev/reference/specifications/custom-operations/) for full details. |
| 124 | + |
| 125 | +**Validation**: Uses `jsonschema` crate to validate flag configs against [flagd-schemas](https://github.com/open-feature/flagd-schemas). Two modes: |
| 126 | +- Strict (default): Reject invalid configs |
| 127 | +- Permissive: Accept with warnings (for legacy compatibility) |
| 128 | + |
| 129 | +**Flag State Management** (storage/mod.rs): Thread-local storage for flag configurations. `update_state` detects and reports changed flags (added, removed, or mutated). |
| 130 | + |
| 131 | +## Important Implementation Details |
| 132 | + |
| 133 | +### Building for WASM |
| 134 | + |
| 135 | +**Critical Build Flags** (Cargo.toml:46-51): |
| 136 | +```toml |
| 137 | +[profile.release] |
| 138 | +opt-level = "z" # Optimize for size |
| 139 | +lto = true # Link-time optimization |
| 140 | +codegen-units = 1 # Single codegen unit for better optimization |
| 141 | +strip = true # Strip symbols |
| 142 | +panic = "abort" # Remove panic unwinding infrastructure |
| 143 | +``` |
| 144 | + |
| 145 | +**No Default Features for WASM**: Always build with `--no-default-features` to exclude CLI dependencies (clap, colored) from WASM binary. |
| 146 | + |
| 147 | +### Memory Safety Rules |
| 148 | + |
| 149 | +1. **Never panic in WASM exports**: All errors must be returned as JSON error responses |
| 150 | +2. **Always validate UTF-8**: Use `string_from_memory()` which returns Result |
| 151 | +3. **Pointer lifetime**: WASM memory is stable within a single function call but may be reallocated between calls |
| 152 | +4. **Safety comments required**: All `unsafe` blocks must have `// SAFETY:` comments explaining why they're safe |
| 153 | + |
| 154 | +### Error Handling Patterns |
| 155 | + |
| 156 | +**JSON Logic Evaluation** (lib.rs:173-175): |
| 157 | +```rust |
| 158 | +match logic.evaluate_json(&rule_str, &data_str) { |
| 159 | + Ok(result) => EvaluationResponse::success(result), |
| 160 | + Err(e) => EvaluationResponse::error(format!("{}", e)), |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +**Flag Evaluation** (evaluation.rs): Returns `EvaluationResult` with standardized error codes: |
| 165 | +- `FLAG_NOT_FOUND` - Flag key not in configuration |
| 166 | +- `PARSE_ERROR` - JSON parsing or rule evaluation error |
| 167 | +- `TYPE_MISMATCH` - Resolved value doesn't match expected type |
| 168 | +- `GENERAL` - Other errors |
| 169 | + |
| 170 | +Resolution reasons: `STATIC`, `DEFAULT`, `TARGETING_MATCH`, `DISABLED`, `ERROR`, `FLAG_NOT_FOUND` |
| 171 | + |
| 172 | +### Testing Philosophy |
| 173 | + |
| 174 | +**Integration Tests** (tests/integration_tests.rs): 72 comprehensive tests covering: |
| 175 | +- Basic JSON Logic operations |
| 176 | +- All custom operators (fractional, sem_ver, starts_with, ends_with) |
| 177 | +- Memory management |
| 178 | +- Edge cases and error handling |
| 179 | +- State management and flag evaluation |
| 180 | +- Type-specific evaluation functions |
| 181 | +- Context enrichment ($flagd properties) |
| 182 | + |
| 183 | +**CLI Tests** (tests/cli_tests.rs): End-to-end tests for the flagd-eval binary. |
| 184 | + |
| 185 | +**When to Run Tests**: |
| 186 | +- ✅ After making code changes that affect behavior |
| 187 | +- ✅ Before creating a PR |
| 188 | +- ✅ When explicitly requested by user |
| 189 | +- ❌ During initial exploration or code reading |
| 190 | +- ❌ When just browsing documentation |
| 191 | + |
| 192 | +### Common Workflows |
| 193 | + |
| 194 | +**Adding a New Custom Operator**: |
| 195 | +1. Create new file in `src/operators/` (e.g., `my_operator.rs`) |
| 196 | +2. Implement `datalogic_rs::Operator` trait |
| 197 | +3. Register in `src/operators/mod.rs` via `create_evaluator()` |
| 198 | +4. Add tests in both unit tests and `tests/integration_tests.rs` |
| 199 | +5. Document in README.md under "Custom Operators" section |
| 200 | +6. Consider adding to CLI's `operators` command output |
| 201 | + |
| 202 | +**Modifying Flag Evaluation Logic**: |
| 203 | +1. Primary logic is in `src/evaluation.rs` |
| 204 | +2. Context enrichment happens in `evaluate_flag()` function |
| 205 | +3. State retrieval uses thread-local storage via `get_flag_state()` |
| 206 | +4. Always maintain backward compatibility with flagd provider specification |
| 207 | +5. Test with targeting rules, disabled flags, and missing flags |
| 208 | + |
| 209 | +**Memory Management Changes**: |
| 210 | +1. All WASM-facing functions must use packed u64 returns |
| 211 | +2. Use `string_to_memory()` to allocate and pack results |
| 212 | +3. Use `string_from_memory()` to read inputs (handles UTF-8 validation) |
| 213 | +4. Document caller responsibilities in function doc comments |
| 214 | +5. Test with the Java example in `examples/java/` |
| 215 | + |
| 216 | +## Git Workflow & Commit Practices |
| 217 | + |
| 218 | +**Make Regular Commits**: Commit your changes frequently with clear, descriptive messages. Don't wait until the end of a large feature to commit. |
| 219 | + |
| 220 | +**Follow Conventional Commits Format**: Use the same format for regular commits as required for PR titles: |
| 221 | + |
| 222 | +``` |
| 223 | +<type>(<scope>): <description> |
| 224 | +
|
| 225 | +[optional body] |
| 226 | +
|
| 227 | +[optional footer] |
| 228 | +``` |
| 229 | + |
| 230 | +**Examples**: |
| 231 | +```bash |
| 232 | +# Feature commits |
| 233 | +git commit -m "feat(operators): add new string matching operator" |
| 234 | +git commit -m "feat(evaluation): support nested context properties" |
| 235 | + |
| 236 | +# Bug fix commits |
| 237 | +git commit -m "fix(memory): correct pointer alignment in alloc function" |
| 238 | +git commit -m "fix(fractional): handle empty bucket key correctly" |
| 239 | + |
| 240 | +# Documentation commits |
| 241 | +git commit -m "docs: update API examples in README" |
| 242 | +git commit -m "docs(operators): clarify sem_ver caret range behavior" |
| 243 | + |
| 244 | +# Refactoring commits |
| 245 | +git commit -m "refactor(storage): simplify flag state management" |
| 246 | + |
| 247 | +# Test commits |
| 248 | +git commit -m "test(fractional): add edge case for zero-weight buckets" |
| 249 | + |
| 250 | +# Chore commits |
| 251 | +git commit -m "chore(deps): update datalogic-rs to 4.1" |
| 252 | +git commit -m "chore: remove unused chrono shim" |
| 253 | +``` |
| 254 | + |
| 255 | +**Commit Message Guidelines**: |
| 256 | +- Use imperative mood ("add feature" not "added feature") |
| 257 | +- Keep subject line under 72 characters |
| 258 | +- Add body for complex changes explaining why, not what |
| 259 | +- Reference issues when relevant: `Closes #123` |
| 260 | + |
| 261 | +**When to Commit**: |
| 262 | +- ✅ After completing a logical unit of work |
| 263 | +- ✅ Before switching to a different task |
| 264 | +- ✅ After fixing a bug |
| 265 | +- ✅ After adding tests |
| 266 | +- ✅ Before taking a break or ending work session |
| 267 | +- ❌ Don't commit broken code (unless marked with WIP) |
| 268 | +- ❌ Don't commit commented-out code or debug statements |
| 269 | + |
| 270 | +## Release Process |
| 271 | + |
| 272 | +This project uses [Release Please](https://github.com/googleapis/release-please) for automated releases: |
| 273 | + |
| 274 | +- **PR Titles Must Follow Conventional Commits**: The PR title becomes the commit message (squash merge) |
| 275 | +- Format: `<type>(<scope>): <description>` |
| 276 | +- Types that trigger releases: |
| 277 | + - `feat:` - Minor version bump (new feature) |
| 278 | + - `fix:` - Patch version bump (bug fix) |
| 279 | + - `perf:` - Patch version bump (performance improvement) |
| 280 | + - `feat!:` or `BREAKING CHANGE:` - Major version bump |
| 281 | +- Types for changelog only (no release): `docs:`, `chore:`, `test:`, `ci:`, `refactor:`, `style:`, `build:` |
| 282 | + |
| 283 | +**PR Title Validation**: GitHub workflow (`.github/workflows/pr-title.yml`) automatically validates format. |
| 284 | + |
| 285 | +## Dependencies |
| 286 | + |
| 287 | +**Core Production**: |
| 288 | +- `datalogic-rs` (4.0) - JSON Logic implementation |
| 289 | +- `serde`, `serde_json` - JSON serialization (no_std compatible with alloc) |
| 290 | +- `jsonschema` (0.37) - Flag configuration validation |
| 291 | + |
| 292 | +**CLI Only** (excluded from WASM): |
| 293 | +- `clap` - Command-line argument parsing |
| 294 | +- `colored` - Terminal colors |
| 295 | + |
| 296 | +**Dev**: |
| 297 | +- `assert_cmd`, `predicates` - CLI integration testing |
| 298 | + |
| 299 | +## Cross-Language Integration |
| 300 | + |
| 301 | +This WASM module is embedded in multiple language providers. Key integration patterns: |
| 302 | + |
| 303 | +**Java (Chicory)**: |
| 304 | +```java |
| 305 | +// Load WASM, get exports for alloc/dealloc/evaluate_logic |
| 306 | +// Allocate memory for inputs, write UTF-8 JSON |
| 307 | +// Call function, unpack u64 result |
| 308 | +// Read result from memory, deallocate all memory |
| 309 | +``` |
| 310 | + |
| 311 | +See `examples/java/FlagdEvaluatorExample.java` for complete working example. |
| 312 | + |
| 313 | +**General Pattern**: |
| 314 | +1. Load WASM module |
| 315 | +2. Get function exports (alloc, dealloc, evaluate_logic, update_state, evaluate) |
| 316 | +3. For each call: |
| 317 | + - Allocate memory for inputs using `alloc()` |
| 318 | + - Write UTF-8 encoded JSON strings to WASM memory |
| 319 | + - Call evaluation function with pointers and lengths |
| 320 | + - Unpack returned u64 (ptr = upper 32 bits, len = lower 32 bits) |
| 321 | + - Read result JSON from WASM memory |
| 322 | + - Free all allocations using `dealloc()` |
| 323 | + |
| 324 | +**Memory Lifecycle**: Host application owns all memory allocation/deallocation decisions. WASM module only allocates result memory internally. |
| 325 | + |
| 326 | +## Related Documentation |
| 327 | + |
| 328 | +- **Flagd Provider Specification**: https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md |
| 329 | +- **In-Process Resolver**: https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/providers.md#in-process-resolver |
| 330 | +- **Custom Operations Spec**: https://flagd.dev/reference/specifications/custom-operations/ |
| 331 | +- **Flag Definitions Schema**: https://flagd.dev/reference/flag-definitions/ |
| 332 | +- **JSON Logic**: https://jsonlogic.com/ |
| 333 | +- **datalogic-rs**: https://github.com/cozylogic/datalogic-rs |
| 334 | +- **Chicory WASM Runtime**: https://github.com/nicknisi/chicory |
| 335 | + |
| 336 | +## GitHub Copilot Instructions |
| 337 | + |
| 338 | +The `.github/copilot-instructions.md` file contains extensive context about this project including: |
| 339 | +- Architecture and purpose |
| 340 | +- WASM function exports and memory management |
| 341 | +- Testing guidelines (when to run tests vs when to just read them) |
| 342 | +- Custom operators implementation details |
| 343 | +- Pull request conventions |
| 344 | +- Integration patterns with host languages |
| 345 | + |
| 346 | +Refer to that file for additional context when working on this repository. |
0 commit comments