https://github.com/rendis/feature-evaluator
Feature flag system with rule-based evaluation, segment targeting, and pack-based feature bundling
https://github.com/rendis/feature-evaluator
feature-flags feature-management feature-toggles go postgresql react redis rule-engine segment-targeting typescript
Last synced: about 2 months ago
JSON representation
Feature flag system with rule-based evaluation, segment targeting, and pack-based feature bundling
- Host: GitHub
- URL: https://github.com/rendis/feature-evaluator
- Owner: rendis
- License: mit
- Created: 2026-03-09T23:56:38.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-07T20:24:06.000Z (2 months ago)
- Last Synced: 2026-04-07T22:26:25.573Z (2 months ago)
- Topics: feature-flags, feature-management, feature-toggles, go, postgresql, react, redis, rule-engine, segment-targeting, typescript
- Language: Go
- Size: 7.31 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
Self-hosted feature flag platform with expression-based rules, user segments, feature packs,
A/B experiments, and a full admin console. Built for teams that need fine-grained control
over progressive delivery without vendor lock-in.
Quick Start ·
Features ·
Architecture ·
API Usage ·
API Reference ·
Deployment
---
## Admin Console
More screenshots
**Feature list** — search, filter by status/type/tag/environment, toggle features inline

**Segments** — user segment management with CSV import and tenant-scoped membership

## Key Features
**Evaluation Engine**
- Expression-based rules powered by [expr-lang](https://github.com/expr-lang/expr) with compiled bytecode and LRU cache
- Percentage rollouts with deterministic, monotonic user bucketing (FNV-1a)
- Multi-environment targeting (dev, staging, production)
- [OFREP](https://openfeature.dev/specification/appendix-c-ofrep/) compatible for vendor-agnostic SDK integration
**Segments & Targeting**
- Import thousands of users via CSV with tenant-scoped membership
- Use `inSegment("beta-users")` directly in rule expressions
- Batch segment resolution — pre-scans all rules, resolves memberships in one pass
**Feature Packs**
- Bundle features into packs and activate per tenant, campus, or program
- Pack inheritance chains and tier-based feature gating
- Trial periods with automatic expiration
**Experimentation**
- Full A/B test lifecycle: draft → running → paused → completed
- Deterministic variant assignment, exposure tracking, conversion reporting
- Wilson score confidence intervals for statistical results
**Admin Console**
- React 19 SPA with dark/light theme and i18n (Spanish default, English)
- RBAC with 4 roles: Owner, Admin, Editor, Viewer
- Multi-workspace isolation — all entities scoped per workspace
**Operations**
- Scheduled rollouts — program future changes with background worker execution
- Immutable change history with field-level diffs and actor tracking
- External HTTP validation per rule with circuit breakers (fail-open/closed)
- Rate limiting and Redis caching (fail-open design)
## Architecture
```mermaid
graph TD
SDK["Client / SDK"] -->|POST /features/eval| API
OFSDK["OpenFeature SDK"] -->|POST /ofrep/v1/evaluate/flags| API
Console["Admin Console
React 19 + TanStack"] -->|/features/admin/*| API
API["Feature Evaluator
Go 1.25 · Gin"]
API --> Engine["Rule Engine
expr-lang · LRU cache"]
API --> PG["PostgreSQL
Features · Rules · Segments"]
API --> Redis["Redis
Cache · Rate limit"]
Engine -.->|Optional| Ext["External APIs
Circuit breaker protected"]
API -.- Worker["Schedule Worker
30s polling · skip-locked"]
Worker --> PG
```
### Evaluation Pipeline
```mermaid
flowchart LR
A["Lookup\nfeature"] --> B["Auth\nprofile"]
B --> C["Enabled?"]
C --> D["Schedule\ngates"]
D --> E["Environment\nfilter"]
E --> F["Trial\ncheck"]
F --> G["Experiment\noverride"]
G --> H["Resolve\nsegments"]
H --> I["Pack\nactivation"]
I --> J["Evaluate\nrules"]
J --> K["Default\nvalue"]
```
> Rules are evaluated by priority (ascending). First match wins. Each rule can have rollout percentage, external API bindings, and source bindings. If no rule matches, the feature's `defaultValue` is returned.
## Tech Stack
| Layer | Technology |
| ----------- | -------------------------------------------------------------------------------------------- |
| Backend | Go 1.25, Gin, PostgreSQL (pgx/v5), Redis |
| Rule Engine | [expr-lang/expr](https://github.com/expr-lang/expr) — compiled bytecode, 10K-entry LRU cache |
| Frontend | React 19, Vite 7, TypeScript 5.9, TanStack Router + Query |
| UI | Tailwind CSS v4, shadcn/ui, Radix UI, OKLCH theming |
| Auth | OIDC (any provider), PKCE S256, API keys |
| State | Zustand (client), TanStack Query (server) |
| i18n | react-i18next — Spanish (default), English |
| Protocol | OFREP (OpenFeature Remote Evaluation Protocol) |
## Quick Start
**Prerequisites:** Go 1.25+, Node 22+, pnpm, PostgreSQL, Docker
```bash
# 1. Start Redis
make redis
# 2. Configure backend (copy and edit server/.env)
cp server/.env.example server/.env
# Set DATABASE_URL, REDIS_URI, and generate AUTH_SECRETS_MASTER_KEY:
# openssl rand -base64 32
# 3. Start backend (port 8080) + frontend (port 5173)
make dev
```
> **Dev mode:** Auth is disabled by default (`AUTH_DISABLED=true`). A mock user with `owner` role is injected automatically. The backend runs a preflight check and fails fast if PostgreSQL or Redis are unreachable.
## API Usage
### Single Feature Evaluation
```bash
curl -X POST http://localhost:8080/features/eval \
-H "Content-Type: application/json" \
-d '{
"featureKey": "checkout-v2",
"context": {
"user": { "id": "u-123", "plan": "enterprise" },
"tenant": { "id": "acme-corp" }
},
"environment": "production"
}'
```
```jsonc
// Response
{
"featureKey": "checkout-v2",
"value": true,
"valueType": "boolean",
"reason": "matched_rule",
"matchedRule": { "name": "Enterprise users", "priority": 0 },
"segments": [{ "key": "beta-users", "isMember": true }],
"evaluatedAt": "2026-03-10T12:00:00Z",
}
```
### Bulk Evaluation
```bash
curl -X POST http://localhost:8080/features/eval/bulk \
-H "Content-Type: application/json" \
-d '{
"features": [
{ "featureKey": "checkout-v2", "context": { "user": { "id": "u-123" } } },
{ "featureKey": "dark-mode", "context": { "user": { "id": "u-123" } } }
]
}'
```
### OFREP (OpenFeature)
```bash
curl -X POST http://localhost:8080/ofrep/v1/evaluate/flags/checkout-v2 \
-H "Content-Type: application/json" \
-d '{ "context": { "targetingKey": "u-123", "tenantId": "acme-corp" } }'
```
> OFREP flat context is auto-mapped: `targetingKey` → `user.id`, `tenantId` → `tenant.id`. See [API Reference](docs/api-reference.md) for all endpoints.
## Expression Engine
Rules use [expr-lang](https://github.com/expr-lang/expr) expressions with these built-in variables and functions:
| Context | Available Fields |
| ------------------- | ------------------------------------------------------------- |
| `user` | `user.id`, `user.plan`, `user.email`, ... (from eval context) |
| `tenant` | `tenant.id`, `tenant.name`, ... |
| `campus`, `program` | Same pattern as tenant |
| `authenticated` | `true` if request passed auth profile validation |
| Function | Description |
| --------------------------------- | ---------------------------------- |
| `inSegment("key")` | Check if user belongs to a segment |
| `now()` | Current time |
| `dateBefore(field, "2026-12-31")` | Date comparison |
| `dateAfter(field, "2026-01-01")` | Date comparison |
**Example expressions:**
```
user.plan == "enterprise" && inSegment("beta-users")
```
```
authenticated && tenant.id in ["acme", "globex"] && dateAfter(now(), "2026-06-01")
```
> Full guide: [Rule Engine Documentation](docs/rule-engine.md)
## Project Structure
```
feature-evaluator/
├── server/ # Go backend
│ ├── cmd/server/ # Entrypoint + preflight checks
│ ├── internal/
│ │ ├── domain/ # Business logic (zero external deps)
│ │ │ ├── evaluation/ # Eval pipeline + rollout bucketing
│ │ │ ├── feature/ # Feature + rule CRUD
│ │ │ ├── pack/ # Feature packs + activations
│ │ │ ├── segment/ # Segments + membership
│ │ │ ├── experiment/ # A/B testing lifecycle
│ │ │ ├── schedule/ # Scheduled rollouts + worker
│ │ │ └── ... # member, apikey, tag, changelog, workspace, tier
│ │ ├── engine/ # expr-lang compiler, LRU cache, security
│ │ ├── external/ # HTTP validation + circuit breaker
│ │ ├── handler/ # Gin HTTP handlers (DTO mapping only)
│ │ ├── dto/ # Request/response types + mappers
│ │ ├── storage/postgres/ # Repository implementations
│ │ └── server/ # Routes + middleware stack
│ └── pkg/apierror/ # Structured API errors (code + i18n messageKey)
├── console/ # React frontend
│ └── src/
│ ├── routes/ # TanStack Router (file-based)
│ ├── components/ # Features, rules, segments, packs, experiments, dashboard
│ ├── api/ # Typed API client
│ ├── auth/ # OIDC + RBAC guards
│ ├── queries/ mutations/ # TanStack Query hooks
│ └── stores/ # Zustand (workspace, sidebar)
├── docs/ # Architecture, API reference, MCP setup, deployment
└── Makefile # All dev commands
```
## Commands
| Command | Description |
| ---------------- | --------------------------------------------------------------- |
| `make dev` | Start backend + frontend (PostgreSQL and Redis must be running) |
| `make server` | Go backend on port 8080 |
| `make console` | React frontend on port 5173 |
| `make redis` | Start Redis via Docker Compose |
| `make quality` | Full CI gate: lint + test + typecheck |
| `make lint` | Go (golangci-lint) + React (ESLint) |
| `make test` | Go (with `-race`) + React (Vitest) |
| `make typecheck` | TypeScript type checking |
| `make fmt` | gofmt + goimports + Prettier |
| `make swagger` | Regenerate OpenAPI spec from handler annotations |
## API Routes
| Group | Auth | Key Routes |
| ----------- | -------------------- | ------------------------------------------------------------------------ |
| Health | None | `GET /features/healthz`, `/features/readyz` |
| Eval | Bearer / API key | `POST /features/eval`, `/eval/bulk`, `/eval/active`, `/eval/conversions` |
| OFREP | Feature auth profile | `POST /ofrep/v1/evaluate/flags/:key`, `/ofrep/v1/evaluate/flags` |
| Features | JWT (admin) | `GET/POST/PUT/DELETE /features/admin/features`, `PATCH .../toggle` |
| Rules | JWT (admin) | `CRUD /features/admin/features/:key/rules`, `PUT .../reorder` |
| Packs | JWT (admin) | `CRUD /features/admin/packs`, `POST/DELETE .../activate` |
| Segments | JWT (admin) | `CRUD /features/admin/segments`, `POST .../members/import` |
| Experiments | JWT (admin) | `CRUD /features/admin/experiments`, lifecycle transitions |
| Schedules | JWT (admin) | `POST/GET/DELETE /features/admin/features/:key/schedules` |
| Changelog | JWT (admin) | `GET /features/admin/changelog` |
| Workspaces | JWT (owner) | `CRUD /features/admin/workspaces`, archive/restore |
| Members | JWT (admin) | `CRUD /features/admin/members`, ownership transfer |
| Dashboard | JWT (admin) | `GET /features/admin/dashboard/{stats,activity,metrics,...}` |
## Roles & Permissions
| Permission | Owner | Admin | Editor | Viewer |
| ------------------------ | :---: | :---: | :----: | :----: |
| features.read | Y | Y | Y | Y |
| features.write | Y | Y | Y | — |
| segments.read / write | Y | Y | Y | Y / — |
| packs.read / write | Y | Y | Y | Y / — |
| experiments.read / write | Y | Y | Y | Y / — |
| members.manage | Y | Y | — | — |
| settings.manage | Y | Y | — | — |
| workspace.delete | Y | — | — | — |
| ownership.transfer | Y | — | — | — |
## Documentation
| Document | Description |
| -------------------------------------- | ------------------------------------------- |
| [Architecture](docs/architecture.md) | System design, data flow, caching strategy |
| [API Reference](docs/api-reference.md) | Full endpoint reference with examples |
| [Configuration](docs/configuration.md) | All environment variables and setup |
| [Rule Engine](docs/rule-engine.md) | Expression language, functions, security |
| [Deployment](docs/deployment.md) | Docker, production checklist, health checks |
| [UI Flows](docs/UI-FLOW.md) | Admin console screen-by-screen walkthrough |
## MCP Integration
Uses `mcp-openapi-proxy` — auto-generates MCP tools from the Swagger spec. Each API endpoint becomes a callable MCP tool.
Install: `go install github.com/rendis/mcp-openapi-proxy/cmd/mcp-openapi-proxy@latest`
See [`docs/mcp_setup.md`](docs/mcp_setup.md) for full setup (Claude Code, Codex, Gemini CLI) and [`skills/feature-evaluator/SKILL.md`](skills/feature-evaluator/SKILL.md) for tool reference.
## Deployment
Both backend and frontend include production-ready multi-stage Dockerfiles:
- **Backend:** Go binary on Alpine 3.21, non-root user, port 8080
- **Frontend:** Vite build → Nginx 1.27 Alpine, SPA routing, API proxy, port 80
```bash
docker build -t feature-evaluator-server -f server/Dockerfile server/
docker build -t feature-evaluator-console -f console/Dockerfile console/
```
> Full guide: [Deployment Documentation](docs/deployment.md)
## Contributing
1. Fork the repository
2. Create a feature branch
3. Run `make quality` before submitting (lint + test + typecheck)
4. Open a pull request
**Conventions:** Clean architecture (domain has zero external deps), handlers only map DTOs, errors use `pkg/apierror` with i18n messageKeys, Go errors wrapped with context (`fmt.Errorf("doing X: %w", err)`).
## License
MIT — see [LICENSE](LICENSE)