# Rulebricks Documentation
> Decision automation platform for visual rule modeling and business logic.
## User Guide
/
Rulebricks is a decision automation platform that helps you model important decisions in your business visually as "rules", and then integrate them into an automated workflow. Learn how to get started with Rulebricks here.
# User Guide
Learn how our visual decision modeling approach can help your business make
complex, embedded logic surprisingly accessible.
}
title="What is a rule?"
description="The building blocks of decision automation."
href="/getting-started/what-is-a-rule"
/>
}
title="Start building"
description="Create your first rule in under 5 minutes."
href="/getting-started/start-building"
/>
}
title="API/SDK Reference"
description="Complete API docs and SDK examples."
href="/api-reference"
/>
}
title="Integration guide"
description="How to use the rules you've built."
href="/getting-started/integration"
/>
}
title="Rulebricks Flows"
description="Chain rules together for complex workflows."
href="/integrating-rules/rule-flows"
/>
}
title="Private deployments"
description="Self-host with full control."
href="/private-deployment/quick-start"
/>
## Condition Priorities
/advanced-features/condition-priorities
Condition priorities allow you to specify arbitrary subgroup "flights" of conditions that should be evaluated before others. Learn how to set them up here.
# Condition Priorities
By default in Rulebricks, conditions are evaluated in the order they are defined in the decision table, from top to bottom, and the first condition row where every comparison is satisfied is the one that is used to calculate the result of the rule.
But in some more complex scenarios, you may want a single decision table, but certain subgroups of conditions to take precedence over others. This is where condition priorities come in.
Priorities allow you to specify arbitrary subgroup "flights" of conditions that should be evaluated before others. This is useful when you have a set of conditions that should be evaluated first, and only if none of those conditions are met, should the other conditions be evaluated, and so on.
## Setting priorities
To set condition priorities, open the Row Settings menu for the condition row you want to set a priority for, and set a priority number. Higher numbers are evaluated first, and lower numbers are evaluated later. If two rows have the same priority, they are evaluated in the order they appear in the decision table, but in isolation, as if all rows with lower priority numbers do not exist, and so on.

Row priorities cannot be configured if a row is already a member of a group. If you want to set a priority for a row that is already a member of a group, you must first ungroup the row, set the priority, and then regroup the row. Alternatively, you can set the priority for the group itself, which will apply to all rows in the group.
After you've set priorities for your rows, you can see the priority number in the row itself, in the indicators on the left.

You might note a new symbol in the row indicator in the above image– a little clock icon. This indicates if a particular condition is in or out of schedule, which we'll cover in the next section.
## Groups & Hit Policies
/advanced-features/groups-and-hit-policies
Groups in Rulebricks allow you to organize conditions in a decision table in a way that makes sense for your use case. Learn how to create groups and configure hit policies here.
# Groups & Hit Policies
In Rulebricks, conditions in a decision table can be grouped in various ways. Often, this grouping functionality provides an elegant solution to situations where your decision tables would otherwise become large and unwieldy.
Let's take a look at how groups work in Rulebricks, and how you can use them to organize your decision tables.
---
### Creating a group
To create a group, select the conditions you want to group together, and select the "Group" icon from the toolbar. If you are confused which icon is the "Group" icon, hover over the icons to see their tooltips.

### Explore the group's settings
Once you've created a group, you can open the group settings menu by clicking either the group indicator, or any row's settings icon within the group.

You should see a menu that allows you to configure the group's hit policy, and other settings.

### Configure the group's hit policy
The hit policy determines how the output of the decision table is calculated. They are outlined in the menu you just opened, and we'll cover them in more detail below.

1. #### Fill Values
For each satisfied condition in the group, the *Fill Values* hit policy will identify any new non-null response dimensions and add them to the output object.
For example, if you have a group with two conditions, and the first condition returns a percentage discount of 10% but a flat price of `null`, and the second condition returns a flat price of $5 but a percentage discount of `null`, this policy will return an object with both the percentage discount and the flat price. This is useful when you want to create a decision table that incrementally updates response data dimensions (i.e. first you have a set of rows that decides what the "percentage_discount" should be, then you have a set of rows that decides what the "flat_price_discount" should be, and you want to return both).
2. #### Add Values
*Add Values* in Rulebricks groups is similar to *Fill Values*, but rather than simply filling in non-null response dimensions, it will actually try to add non-null values together. This is useful when you want to return a single value that is the sum or score of all the values from multiple response rows satisfied in the decision table.
3. #### Compute Values
*Compute Values* is the most advanced hit policy in Rulebricks and allows you to define a custom accumulation pattern for each column. This policy is useful if you want to add up the results of multiple satisfied rows for some columns, but not for others. It's also useful if you'd like more control over how you combine responses from multiple satisfied rows, rather than just adding up everything– this policy also allows you to write your own accumulation function, should you need it.
A good example of this policy in action can be found on [this use-case page](https://rulebricks.com/examples/rules/investment-portfolio-builder).
---
You can visually identify groups in your decision table by the group indicator, which is a small colored circle that appears to the left of the group's conditions. Different groups are assigned random unique colors.

## Scheduled Conditions
/advanced-features/scheduled-conditions
Scheduled conditions in Rulebricks allow you to change how you make a particular decision based on the current time of week & time of day. Learn how to set up scheduled conditions here.
# Scheduled Conditions
Decision tables are a powerful way to make decisions based on data, but what if you need to change how you make a particular decision based on the current time of week & time of day? Scheduled conditions are your answer, and are remarkably easy to set up.
## Configuring a schedule
First, open a condition row's settings menu by clicking the gear icon in the left margin of the row. You should see a menu that allows you to configure the row's schedule.

Check any days of the week you want the condition to be active, and set a start and end time in UTC for the condition.

If you'd like to disable the schedule for a row, simply uncheck all days of the week.
## Viewing if a condition is in schedule
After you've set a schedule for a condition, you can see if it's currently in schedule simply by looking at the row indicator in the left margin of the row. If the row is in schedule, you'll see a green clock icon, and if it's out of schedule, you'll see a red one. Again, **schedule times are in UTC**, so be sure to adjust accordingly for your local time zone, or you may end up confused about why a scheduled condition isn't working as expected.

In this example, the first condition is out of schedule, and would currently be disregarded if I were to run that Rule, and the last condition is in schedule.
## Dynamic Values
/advanced-features/values-and-functions
Store reusable values and write custom JavaScript functions to use across your rules.
# Dynamic Values
Your tax rate appears in twelve rules. When it changes from 8% to 8.5%, do you want to update twelve rules—or one value?
Dynamic Values solve this problem for you by storing numbers, strings, and other data types centrally. Reference them in any rule, and update them in one place.
Changes to values propagate instantly, and using values doesn't detract from how performant your rules are because values are deeply integrated into our caching infrastructure.
## Ad-hoc values
### Create a value
Go to **Team → Values** and click **Create New Value**.

### Fill in the value's details

### Use it in a rule
In the rule editor, click a condition or result cell. Toggle to **variable mode** and select your value from the dropdown.
Note that you'll only see your value if it's the right type of data for the operator the cell is using. For example, if your operator is "Greater than", then you'll only see your Values of type number.

## Bulk importing
You'll often want to bring in a large number of values at once to start using vocabulary from your business right away. Rulebricks offers you three ways to do this quickly.
First, our values UI allows you to simply import large, potentially nested JSON dictionaries that simply contain key value pairs.
Use the "From JSON" tab to continue with this option.

Second, an API endpoint is also available to create and update dynamic values. Start by looking at our [Dynamic Values API](https://rulebricks.com/docs/api-reference#tag/values).
Finally, if you have JSON Schema on hand, or even a DDL or other similar schema artifact, you can upload your schema as an Object (or use smart import if you're bringing a DDL/non-standard format).
Rulebricks will automatically scan your schema for enums/ranges, and create values based on them in your workspace.
## Functions
Functions let you write JavaScript that computes results dynamically, calculating results based on input and other output values.
Functions can be authored here as arrow expressions. Some libraries are available to assist with complex operations.
```javascript
((quantity, price) => (quantity > 100 ? price * 0.9 : price))
```
### Create a function
Go to **Dashboard → Values** and click **Create New Value** – functions are just a kind of Dynamic Value.
Select the function type, and write in a JavaScript arrow expression.
### Use it in a rule
Add a **Function** type column to your rule's **response**, or update an existing column's type accordingly. Type `$` to see available functions, including the one you've just made.

## Analysis Tools
/analysis-tools
Rulebricks provides analysis tools to help you understand how your rules perform in production and how changes might affect outcomes.
# Analysis Tools
Rulebricks provides two powerful analysis tools in the rule editor to help you understand how your rules perform in production and how changes might affect outcomes.
## Available Tools
### Impact Analysis
Test how changes to your rule conditions would affect outputs **before deploying**. Impact analysis replays historical requests through modified conditions to show you exactly what would change.

**Use it when:**
- You're considering changing a condition value
- You want to compare the current rule version against a previous one
- You need to understand the downstream effects of a rule change
[Learn more about Impact Analysis →](/analysis-tools/impact-analysis)
### Reachability Analysis
See which rows in your decision table are actually being matched by production traffic. Reachability analysis shows you hit frequencies and helps identify dead code or optimization opportunities.

**Use it when:**
- You suspect some rows are never being matched
- You want to optimize rule evaluation order
- You need to understand which conditions are causing the most rejections
[Learn more about Reachability Analysis →](/analysis-tools/reachability-analysis)
## Accessing the Tools
Both tools are available in the **footer bar** of the rule editor and are available on any paid plan. Click **Impact** or **Reachability** to open the respective analysis panel.
Results are based on your rule's execution logs, so you'll need some
production traffic before the analysis becomes meaningful.
For static analysis of rule structure (without production data), see [Rule Warnings](/warnings)—which examines your rule for potential issues like unreachable rows and coverage gaps.
## Impact Analysis
/analysis-tools/impact-analysis
See exactly how rule changes would affect your outputs before deploying. Impact Analysis replays historical requests through modified conditions.
# Impact Analysis
You're about to change your credit score threshold from 700 to 650. How many more applications will get approved? Will your denial reasons shift?
Impact Analysis gives you crystal clear answers to these questions before you deploy anything.

The tool replays a configurable sample of real production requests through your modified rule logic and shows you exactly what would change—approval rates, output distributions, everything.
## Analysis Methods
Two analysis methods are made available, depending on how your organization is using rules in production, and the extent of changes you're trying to simulate.
Test one or more specific condition changes (against the working rule) before publishing them:
1. Click on any condition cell in your rule table
2. Enter the new value you want to test
3. Click **Run Analysis**
The results show how your outputs would shift. If you're lowering a threshold, you'll see exactly how many more requests would match. If you're tightening a condition, you'll see what would get filtered out.
You can also click on multiple condition cells to understand interactions from
multiple changes to different conditions at once.
Compare the current (working) rule against a previously published version:
1. Switch to **Version** mode in the panel
2. Select a previous version from the dropdown
3. Click **Run Analysis**
This is useful when something seems off after a deploy— you can see precisely what changed between versions and how it affected your output distribution.
While it will still run, we do not recommend this approach if your schema
changes often.
## Reading the Results
Results show distribution charts for each output field. The visualization adapts to the data type:

Histogram distributions with before/after comparison. Shows how value distributions shift.

Bar charts showing how often each value would be returned. Boolean outputs show percentage shifts between true/false.

Timeline distributions for date outputs. Useful for rules that set deadlines or expiration dates.
Watch for **null shifts**—when values suddenly become null, it often means requests stopped matching any row. Impact Analysis will surface this in an alert if it detects it.
While computation is done on our servers and included with your Rulebricks
plan, we recommend being very deliberate about the sample size you use–
particularly between the 100k - 1M range; these can be large reports.
## Example: Threshold Optimization
Say you want to approve more loan applications by lowering the credit score requirement from 700 to 650.
1. Click the credit score condition cell
2. Change it to 650
3. Run analysis against the last 500 requests
The results might show:
- Approval rate jumps from 62% to 78%
- Average approved amount drops slightly (riskier applicants qualify for less)
- Denial reason distribution shifts from "low credit" to "high debt ratio"
Now you know exactly what you're signing up for before you ship the change.
## Reachability Analysis
/analysis-tools/reachability-analysis
See which rows in your decision table are actually being matched by production traffic. Find dead code and optimization opportunities.
# Reachability Analysis
Your rule has 47 rows. Which ones actually matter?
Reachability Analysis shows you exactly which rows are being hit by real traffic—and which ones might be dead weight. It analyzes your execution logs to surface usage patterns you wouldn't otherwise see.

## What It Shows
Which rows are doing the heavy lifting:
- Rows sorted by hit frequency, most matched at top
- Percentages showing traffic distribution
- Rows that were **never hit** flagged as potential dead code
Which conditions are blocking matches. This helps you understand why certain rows aren't being reached—maybe a condition is too restrictive, or an earlier row is catching everything.
Which conditions pass most frequently, revealing your typical traffic patterns and the "happy path" through your rule.

The panel header shows summary stats: how many requests matched at least one row, how many matched nothing, and how many rows have zero hits.
High "no match" counts mean inputs are falling through without being handled.
You probably need a catch-all row or broader conditions.
## Finding Dead Rows
A row with 0% hits over 30 days is probably:
- **Unreachable** — an earlier catch-all row blocks it
- **Obsolete** — handles an edge case that never actually occurs
- **Safe to remove** — cleaning up your rule table
Before deleting, make sure to thoroughly investigate _why_ it's not being hit. Some rows handle rare but critical cases— you don't want to remove your fraud detection row just because it only fires twice a month.
## Performance Optimization
If one row handles 80% of your traffic but sits at the bottom of your table, moving it up can meaningfully improve evaluation speed at scale. Rulebricks evaluates top-to-bottom, so putting your high-traffic rows near the top reduces unnecessary condition checks.
Use [Impact Analysis](/analysis-tools/impact-analysis) afterward to verify that reordering didn't change your rule's behavior.
## Large Reports
Computation is handled on our servers– but we encourage users to be prudent about the sample sizes they use as particularly large requests may take several minutes to complete and potentially timeout.
We perform cardinality analysis on your rule internally to optimize reachability reports for massive amounts of data/rows.
## Creating a Form
/building-forms/creating-a-form
Creating a form in Rulebricks is a great way to embed information specific to your business and help users calculate something based on their inputs. Learn how to create a form here.
# Creating a Form
As it turns out, rules in Rulebricks also can be used to create forms & quizzes for your business and/or clients remarkably easily.
This is a particularly good way to **embed information specific to your business** and **help users calculate something** based on their inputs, like a particular pricing quote or eligibility status.
Any form built in Rulebricks can easily be embedded on your website, with your company colors/branding, so it will look right at home when you send it along.
## Video Overview
Below is a brief video overview of what forms in Rulebricks look like.
## Form Guide
Let's walk through creating a form in Rulebricks.
### Create & publish a rule
Create and publish a rule. If you're not sure how to do this, check out our [Getting Started](/getting-started/what-is-a-rule) guide for a brief refresher.
As you're creating your rule, you'll want to make sure you're including in the **Request Object** all the form data you want to collect from your users. This is the data that will be sent to your rule when the form is submitted.
When your rule is ready, click the "Form" button in the top right corner of the rule editor.

### Open the form editor
This will open the form editor. Here you can customize the form to your liking, including editing inputs, changing the form title, description, branding, and more.

### Try out your form
By default your form is already connected to your rule, so when a user submits the form, the data will be sent to your rule and the result will be displayed to the user.
You can try out your rule either in the form editor itself, or by visiting the URL available in the "Sharing" section of the sidebar to see how it might look when embedded on your website or shared with others.

---
Your form is already live and ready to be shared with your users, but you'll probably want to edit the form to make the inputs easier for folks to understand first. In the next section, we'll walk through how to do just that.
## Editing Form Fields
/building-forms/editing-fields
Editing form fields in Rulebricks is a powerful way to customize the form fields and results fields in your rules. Learn how to do it here.
# Editing Form Fields
If you're following along, you might've noticed by now that each attribute of your request object is automatically turned into a form field when you click the "Form" button in the Rulebricks editor, and each attribute in your response object is automatically turned into a result field, i.e. a field that will be displayed to the user after they submit the form.
This is a powerful feature that allows you to quickly create forms that collect the data you need, and display the results you want, without having to write any code.
But how do you customize these form fields? What if you want to change the type of a field, or add a description, or make a field required?
Sometimes, this behavior can be really important, for example if you want to collect text from a user, but you want to make sure they enter one of a few specific options, like "A" or "B". In this case, you might want to change the field type from "Short Text" to "Multiple Choice", and add the options "A" and "B".
## Edit Fields
Let's walk through how to do this.
### Open the form editor
First, open the rule you want to edit in the Rulebricks editor, and click the "Form" button in the top right corner of the rule editor to open the form editor.

### View the form fields
The form editor has three tabs: "Form", "Edit Fields", and "Edit Results". Click on the "Edit Fields" tab to view and edit the form fields.
You should be able to see a dropdown for each field named "Input Variant", describing the particular type of field it is.

Note that by default, Rulebricks will automatically choose the field type
based on the data type of the attribute in your request object. For example,
if the attribute is a number, Rulebricks will choose the "Number" field type.
Not all field types are available for all data types, and there are some cases
where you may not be able to change the field type.
### Edit the field
To change the field type, click on the dropdown and select the field type you want. You can also add a description, make the field required, or add options for certain field types.
In this brief example, we're changing the field type from "Short Text" to "Multiple Choice", and adding a few options.

### Return to your form
Now, click the "Form" tab to return to the form editor, and you should see your changes reflected in the form. In this particular case, what used to be an open-ended text field is now a multiple choice field with specific options I compare against in my rule.

## Edit Results
You can also edit the results fields in the same way, but instead of changing how inputs appear to the user, you're changing how results appear to the user after they submit the form.
These are useful for displaying the results of your rule in a way that makes visual sense to the user, and are purely for display purposes.
### View the form results
To edit the results fields, click on the "Edit Results" tab in the form editor, and check out the "Display Variant" dropdown for each field.

### Edit the display variant
Let's say in this particular case, simply showing the user "Income Verification: true" isn't very helpful, even though it's the result of my rule.
Instead, we can change the display variant to "Outcomes", which will show the user a more human-readable version of the result, like "Income Verified", depending on whether this particular result in the response data is true or false.

### Return to your form
Now, when I head back to my form and try it out, I can see the results of my rule displayed in a way that makes sense to me and my users.

## Sharing Forms
/building-forms/sharing-forms
Sharing forms in Rulebricks is easy, and there are a few ways you can do it. Learn how to share your forms here.
# Sharing Forms
Congrats– you've created a form! Now it's time to share it with the world. There're a few ways you can do this, and a few things you need to keep in mind, so we'll walk through them here.
## Your form is live
When you create a form in Rulebricks, it's automatically public. This means that anyone with the link can access and submit the form. This is great for sharing your form with others, but it also means you need to be careful about who you share the link with.
Each time someone submits your form, the data is sent to your rule, and the result is displayed to the user. This uses up one of your monthly rule executions/runs, so be mindful of this as you share your form. Of course, you can always upgrade your plan if you need more rule executions/runs.
## Sharing your form
To share your form, you can simply copy the URL from the "Sharing" section of the sidebar in the form editor. This URL is unique to your form, and can be shared with anyone you'd like to fill out the form.

## Three options
There are three ways you can share/use your Rulebricks forms. Each of the menus below is available in the sidebar of the form editor, so simply scroll down to find them.
You can share your form by copying the URL from the "Sharing" section of the sidebar in the form editor. This URL is unique to your form, and can be shared with anyone you'd like to fill out the form.

You embed your form on your website by copying the embed code from the "Embed" section of the sidebar. This will allow you to display your form directly on your website, so users can fill it out without leaving your site.

You can use an integration to connect your form to other tools you use in your business. For example, you can use Zapier to connect your form to your CRM, or to send an email notification when someone fills out your form.
This approach focuses less on the way you share the form, and more on what you do with the data once it's submitted.
To use this, simply provide a Webhook URL to the tool you want to integrate with, and we'll send the form data, along with the rule result, to that URL whenever a form is submitted.

## Contexts
/contexts
Contexts hold your decision together while data arrives from different sources at different times. Progressive rule execution for async business processes.
# Contexts
A loan application arrives on Monday. The credit check comes back Wednesday. Employment verification finishes Friday. By the time you have everything you need to make a decision, where did Monday's data go?
**Contexts hold your decision together while the pieces arrive.**

Instead of building infrastructure to track what data you have, what's missing, and when to run your rules, Contexts handle it for you. Define the facts you need, submit them as they arrive from different sources, and your rules execute automatically when everything's ready.
## A Useful Reframing
You should use rules directly when your business already has all the data you need upfront, and you've just been missing the rule piece until now. You should use Contexts when you don't have all the data right away, but you do know the rules that will apply to that data.
Contexts allow you to think about rules and decisions from the perspective of _updates to business entities_, which becomes somewhat apparent when you look at how to use the contexts API:
```javascript
// Credit score arrives from bureau webhook
POST /api/v1/contexts/loan-application/APP-12345
{ "credit_score": 720 }
// Response tells you what's still missing
{
"status": "pending",
"have": ["applicant_id", "credit_score"],
"need": ["annual_income", "employment_verified"]
}
// When the last piece arrives, rules auto-execute
{
"status": "complete",
"state": {
"approval_decision": "approved", // Written by your rule
"max_loan_amount": 250000
}
}
```
As opposed to:
```javascript
// Assumes we have all the data upfront
POST /api/v1/solve/approval-rule
{ "credit_score": 720, "annual_income": 120000, "employment_verified": true }
// Manual write-back/result storage
// ...
```
**Contexts vs Objects:** Objects define data structures for your rules.
Contexts track decision-relevant facts as they arrive over time. If you have
all your data upfront, use Objects. If data arrives progressively from
different sources, use Contexts.
## When to Use Contexts
- Data arrives from multiple sources at unpredictable times (webhooks, user actions, third-party APIs)
- You want rules to execute automatically when their inputs are ready
- Your decision process spans hours, days, or weeks
- You need to track what's been collected vs what's still missing
## Linking Rules & Flows to Contexts
/contexts/binding-rules
Connect rules to Contexts so they read from context facts and write their outputs back automatically.
# Linking Rules & Flows to Contexts
Once your Context(s) are ready, you're ready to create rules & flows that are "bound" to them, priming them to automatically run when your live contexts start receiving data.
Once bound, the rule reads its inputs from context facts and writes its outputs back—no API calls required on your end. All flows that start from that rule are also considered bound and will fire automatically upon associated context updates.
Flow write-back into contexts is accomplished slightly more explicitly via an "Context Operations" step you can find easily from the flow editor.
## Create a Bound Rule
### Open the Rule
Click the "Create rule" button in the dashboard and open the **From Context** tab, which only appears when you have Contexts available.
Provide a name and description for your rule, then click **Continue**.
### Select Context Facts
You will be presented with an interface to select facts from your associated Context as inputs _and outputs_ to this particular rule. If your rule requires data or outputs data you cannot find, update the associated Context by adding the missing fact(s) and return here.
Fields you select as inputs cannot also be selected as outputs. Certain fields may not be eligible for selection as inputs.

As indicated, the rule's request schema fields will map to context facts with matching names, and the rule's response fields will write to context facts with matching names.
### Create Rule
Click the **Create from Context** button at the bottom of your modal, customize your rule, and publish it when you're ready.
To bind flows, simply create a new flow that uses a bound rule as its
origin/first rule.
You can bind multiple decision assets to the same context. They'll execute in
dependency order—a rule that reads `risk_tier` waits for the rule that writes
it.
## Cascading Rules
When Rule A writes a fact that Rule B needs, B executes automatically after A:
```
Facts arrive: credit_score, annual_income
↓
[Credit Check Rule] executes → writes: risk_tier
↓
[Pricing Rule] now has all inputs → executes → writes: rate, max_amount
```
This happens within a single API response. The `cascaded` array shows what ran:
```json
{
"cascaded": [
{ "rule": "credit-check", "result": { "risk_tier": "low" } },
{ "rule": "pricing", "result": { "rate": 4.5, "max_amount": 500000 } }
]
}
```
## Viewing Bound Rules
From the Context dashboard, the **Assets** tab shows all currently bound rules & flows, their execution modes, and which facts they read/write.
## Getting Started with Contexts
/contexts/getting-started
Create your first Context in Rulebricks and see progressive rule execution in action.
# Getting Started with Contexts
Let's build a Customer Context that tracks customer data across multiple sources and automatically runs your bound rules when the required information is available.
### Create a Context
Go to **Dashboard → Contexts** and click **Create Context**.

Name it "Customer" and set `customer_id` as the identity fact—this is what uniquely identifies each customer flowing through the system.
### Define Your Facts
In the Context Editor, add the facts your decisions need:

| Fact | Type | Required |
| ------------- | ------- | -------- |
| `customer_id` | string | Yes |
| `name` | string | No |
| `email` | string | No |
| `tier` | string | Yes |
| `income` | number | Yes |
| `address` | object | No |
| `nicknames` | list | No |
| `subscribed` | boolean | No |
Mark facts as **required** if rules can't execute without them. Facts like `tier` and `income` might be required for your decisions, while profile data like `nicknames` is optional.
### Add a Derived Fact (Optional)
Derived facts calculate automatically from other facts or related entities. Add a `total_spent` fact that aggregates transaction amounts:

```javascript
sum($relations.Transaction, 'amount')
```
This updates whenever a related Transaction is added or modified. You can also create derived facts like `transaction_count` to track how many transactions a customer has.
### Link Your Rule
Create a rule, set its input schema to **From Context**, and select the Customer context. Any rules you create in this way will read from context facts and write their decision results back automatically.
[Learn more about linking decision assets →](/contexts/binding-rules)
### Test It
Use the **Console** tab to explore live customer instances:

Enter a customer ID like `c_001` to fetch the instance and see all base facts (name, tier, income, etc.) and derived facts (transaction_count, total_spent). When required facts are present, bound rules/flows execute automatically– if you've already set those up, you should be able to see their outputs write-back to your live context accordingly.
## Using the API
Submit facts to a context instance via POST:
```bash
curl -X POST "https://rulebricks.com/api/v1/contexts/customer/c_001" \
-H "x-api-key: YOUR_API_KEY" \
-d '{ "tier": "gold", "income": 200000 }'
```
The response shows current state:
```json
{
"status": "pending",
"have": ["customer_id", "tier", "income"],
"need": []
}
```
When all required facts are present and a bound rule exists, the response includes rule execution:
```json
{
"status": "complete",
"cascaded": [
{
"rule": "customer-decision",
"result": { "tier_benefits": ["free_shipping", "priority_support"] }
}
]
}
```
## Key Concepts
- **[Facts and Schema](/contexts/key-concepts/facts-and-schema)** — Derived facts, history tracking, and schema design
- **[Execution Mode](/contexts/key-concepts/progressive-execution)** — How auto-execution and cascades work
- **[Binding Rules](/contexts/binding-rules)** — Connect rules to read from and write to contexts
## Gradient
/contexts/gradient
Gradient automatically discovers decision patterns from your Context data and generates rules for you.
# Gradient
_What if your rules could write themselves?_
Gradient is a new feature coming to Rulebricks that watches the data flowing through your Contexts and automatically surfaces the decision patterns hiding in it. That credit approval workflow your team runs manually? Gradient sees that 94% of applicants with scores above 720 and debt ratios below 30% get approved—and generates the rule for you.
### From Data to Decisions
Your Contexts already capture the facts that matter: what arrived, when, what was decided. Gradient analyzes this history to find what drives outcomes.
**It discovers patterns like:**
- Orders from gold-tier customers over $500 are always expedited
- Applications missing employment verification stall 3x longer than others
- Price adjustments above 15% require manager approval regardless of other factors
These become draft rules you can review, refine, and publish. The implicit knowledge in your business becomes explicit, auditable logic.
### Finding procedural bottlenecks
Beyond rule generation, Gradient identifies where decisions get stuck. Which facts are most often missing when applications stall? Where would a new integration have the highest ROI?
This turns operational friction into actionable insights—you see exactly which data gaps cost you the most.
**Early Access:** Gradient is in active development. If you're interested in
automated rule discovery, [reach out](mailto:support@rulebricks.com) to learn
more or join the early access program.
## Facts and Schema
/contexts/key-concepts/facts-and-schema
Understanding base facts, derived facts, and the identity fact in Rulebricks Contexts.
# Facts and Schema
A Context's schema defines what data it collects. There are three kinds of facts: base facts (the raw data you submit), derived facts (computed automatically), and one special identity fact (what uniquely identifies each instance).
## Base Facts
Base facts are the data points you collect—things like `credit_score`, `annual_income`, or `employment_verified`. They come from API calls, webhooks, user input, or anywhere else in your system.
Each fact has a type (string, number, boolean, date, list, or object) and can be marked as:
- **Required** — Must be present before rules can execute
- **Output only** — Can only be written by rules, not submitted externally
- **Track history** — Stores previous values for temporal queries
## The Identity Fact
One fact must be the **identity fact**—the unique identifier for each context instance. For a loan application context, this might be `application_id`. For transaction processing, `transaction_id`.
The identity appears in your API URLs:
```
POST /api/v1/contexts/loan-application/APP-12345
↑ identity value
```
Identity facts must be strings or numbers, must be required, and can't be
output-only. Once set, they shouldn't change.
## Derived Facts
Derived facts compute automatically from other facts using expressions:
```javascript
// Debt-to-income ratio
monthly_debt / (annual_income / 12)
// Risk category based on score
ifelse(
credit_score >= 750,
'low',
ifelse(credit_score >= 650, 'medium', 'high')
)
// Aggregate from related contexts
sum($relations.transaction, 'total')
// Available functions are listed directly in app UI
```
They update whenever their dependencies change—no manual recalculation needed.
## Relationships
Contexts can relate to each other. A customer context can have many transaction contexts. An transaction context can belong to a customer. These relationships let you build decisions that span multiple entities.
### The Pattern
**Customer** (one) → **Transactions** (many)
A customer context tracks lifetime data: total spend, transaction count, loyalty tier. Each transaction context tracks a single transaction. When an transaction completes, you might want to update the customer's lifetime stats.

### Defining Relationships
In the Context Editor, add a relationship field:
**On the Customer context:**
```
transactions: has_many(Transaction, 'customer_id')
```
**On the Transaction context:**
```
customer_id: string (foreign key)
```
Now transactions link to their customer, and customers can reference their transactions.
### Using Relationships
Derived facts are required for you to expose and use data from related Contexts.
For example, on a Customer context, I might use:
```javascript
// Total spend across all transactions
total_spend: sum($relations.transaction, 'total')
// Number of completed transactions
transaction_count: count($relations.transaction, 'status == "complete"')
// Most recent transaction date
last_transaction_date: max($relations.transaction, 'created_at')
```
These recalculate automatically when any related transaction changes.
### Cascading Across Relationships
When an transaction is marked complete, it can trigger rules on the customer context:
1. Transaction context receives `status: "complete"`
2. Customer's `transaction_count` derived fact recalculates
3. If `transaction_count` crosses a threshold, a loyalty tier rule executes
4. Customer's `loyalty_tier` fact updates
This cascade happens automatically based on the relationships and rule bindings you've defined.
Relationship queries (`$relations.transaction`) only include live, non-expired
context instances– expired transactions won't appear in aggregations. If this
is an issue, consider maintaining a running statistic in your systems and
sending it into the Context via a base fact– we still consider this a data
concern rather than a Rulebricks problem.
### Navigating the Other Direction
From an transaction, access the parent customer:
```javascript
// Get customer's tier for transaction-level pricing
customer_tier: $relations.customer.loyalty_tier
```
This creates a dependency—the transaction context waits for the customer's `loyalty_tier` before derived facts using it can calculate.
## Solvability
A context instance is "solvable" when all required facts are present. The API always tells you where you stand:
```json
{
"status": "pending",
"have": ["application_id", "credit_score"],
"need": ["annual_income", "employment_verified"]
}
```
When `need` is empty, `status` becomes `complete` and bound rules can execute.
## Live Contexts
/contexts/key-concepts/live-contexts
Understanding context instances, their lifecycle, and the have/need pattern for tracking data collection progress.
# Live Contexts
When you submit data to a Context, you create a **live context**—an instance that accumulates facts over time until rules can execute.
- **Context** = the schema definition (what facts you need for loan approval)
- **Live context** = a specific instance (loan application APP-12345's data)
## Lifecycle
A live context is created when you first submit data for a new identity:
```bash
POST /api/v1/contexts/loan-application/APP-12345
{ "annual_income": 85000 }
```
It persists as you submit more facts over time. Each submission merges into the existing state—new fields are added, existing fields are updated, missing fields are left alone.
When all required facts are present, the status changes from `pending` to `complete`, and bound rules can execute.
Eventually, live contexts expire based on their TTL (time-to-live) setting. Expired contexts are no longer accessible—POST requests to that identity create a fresh instance.
## The Have/Need Pattern
Every API response tells you exactly where you stand:
```json
{
"status": "pending",
"have": ["application_id", "credit_score", "annual_income"],
"need": ["employment_verified"],
"expires_at": "2024-01-22T10:30:00Z"
}
```
- `have` — facts with values
- `need` — required facts still missing
- `status` — `pending` until `need` is empty, then `complete`
This makes it easy to show progress in your UI, decide what to fetch next, or debug why a rule isn't executing.
## TTL and Expiration
Set TTL when creating your context (1 hour to 30 days). The clock starts when the instance is created, not when it's last updated.
When a context expires, you can configure a webhook to fire—useful for
cleaning up related resources or notifying users that their process timed out.
## Fetching State
Get the current state of any live context:
```bash
GET /api/v1/contexts/loan-application/APP-12345
```
The response includes all facts (base and derived), the have/need arrays, timestamps, and expiration time.
## Progressive Execution
/contexts/key-concepts/progressive-execution
Rules automatically execute when their inputs are ready. Understand auto-execution and cascading rule chains in Contexts.
# Progressive Execution
The magic of Contexts is that **decisions run automatically when their inputs are ready.**
You don't schedule rule execution or poll for completeness. Simply submit facts as they arrive, and the rules that can run will run.
## How It Works
When you bind a rule to a context, Rulebricks knows what inputs that rule needs. When you submit a fact:
1. All derived facts that depend on it recalculate
2. The system checks if any bound rules now have all their required inputs
3. Rules that are ready execute automatically
4. Their outputs write back to the context as new facts
This happens in a single API response—you submit `employment_verified: true`, and you get back the approval decision your rule computed.
## Cascading Execution
Rules can write facts that other rules depend on. This creates automatic chains:
```
Submit: credit_score = 720
↓
[Risk Assessment Rule] executes
↓
Writes: risk_tier = "low"
↓
[Pricing Rule] executes (was waiting for risk_tier)
↓
Writes: rate = 4.5, max_amount = 500000
```
The response tells you what ran:
```json
{
"status": "complete",
"cascaded": [
{ "rule": "risk-assessment", "result": { "risk_tier": "low" } },
{ "rule": "pricing", "result": { "rate": 4.5, "max_amount": 500000 } }
]
}
```
## Execution Modes
There are very specific situations where automatic decision evaluation on Contexts may be undesirable. While we make Automatic Execution a default, you can also turn it off, preferring to register pending rules/flows manually.
| Mode | When it runs |
| ----------- | ----------------------------------------- |
| **Enabled** | Automatically when all inputs are present |
| **Manual** | Only when you explicitly call `/solve` |
Manual mode is useful when you want to control timing precisely—for example,
waiting for human approval before running a disbursement rule.
## Deterministic Order
When multiple rules can execute, they run in dependency order—a rule that writes `risk_tier` always runs before a rule that reads it. If rules have no dependencies, execution order is stable but arbitrary.
If a rule fails, the cascade stops and the error is returned. Other facts already written in that request remain—partial progress is preserved.
## Webhooks
/contexts/webhooks
Get notified when contexts are solved or expire via webhook callbacks.
# Webhooks
Contexts can call your endpoints when something happens—a rule executes, a context expires, or an error occurs.
## Events
| Event | When it fires |
| ----------------- | ---------------------------------- |
| `context.solved` | A bound rule executed successfully |
| `context.expired` | The context's TTL elapsed |
| `context.error` | A rule execution failed |
## Configuration
In the Context Editor, add a webhook URL and select which events to send.
Rulebricks signs each request with your webhook secret (available in settings). Verify the `X-Rulebricks-Signature` header to ensure requests are authentic.
## Payload Structure
**context.solved:**
```json
{
"event": "context.solved",
"context": "loan-application",
"identity": "APP-12345",
"rule": "credit-check",
"result": {
"risk_tier": "low",
"approved": true
},
"timestamp": "2024-01-15T10:30:00Z"
}
```
**context.expired:**
```json
{
"event": "context.expired",
"context": "loan-application",
"identity": "APP-12345",
"final_state": {
"have": ["application_id", "credit_score"],
"need": ["employment_verified"]
},
"timestamp": "2024-01-22T10:30:00Z"
}
```
The `final_state` on expiration shows what was still missing—useful for
understanding why a decision couldn't be made.
## Why Rulebricks Embedded?
/embedding-rulebricks/about-embedded
Rulebricks Embedded lets engineering teams ship enterprise-grade, user-configurable rule editing inside their own product, without building a rule engine from scratch.
# Why Rulebricks Embedded?

Rulebricks Embedded is for engineering teams who need to deliver **enterprise-grade rule configuration** to their users _now_, without diverting their roadmap for six months to build a rule engine from scratch.
---
When your product needs advanced user-configurable logic—like dynamic pricing, eligibility criteria, or routing rules— many engineering teams face a difficult choice:
1. **Build it yourself**: You spend a month building a decision table UI/backend. You soon discover that features like undo/redo stacks, copy/paste support, change tracking, caching, and performant execution against large datasets are massive engineering sinks.
2. **Use a traditional BRMS**: You get a powerful engine, but you compromise your client experience. Your users are redirected to a third-party dashboard that doesn't look or feel like your product, despite your white-labeling.
Rulebricks Embedded eliminates this compromise. Paired with our SDK, it offers you a unique native-component that allows you to **buy an engine, but build the experience.**
## Built with React, Compatible with Everything
While we ship primarily as a React component, Rulebricks Embedded is designed to work within any modern frontend stack. The component is self-contained and can be mounted into:
- **Angular & Vue**: Using standard wrapper patterns or mounting helpers.
- **Next.js / Remix**: Full support for Server Components and SSR.
- **.NET / Blazor**: Embeddable within Razor pages or Blazor components via JS interop.
- **Internal Tools**: Easily mountable in Retool (custom component) or plain HTML/JS dashboards.
## API Reference & Customization
/embedding-rulebricks/api-reference-and-customization
Complete API reference for Rulebricks Embedded, covering the Rule component props, the server-side token helper, customization options, and troubleshooting.
# API Reference & Customization
This reference covers the `` component props, the server-side helper, customization options, and troubleshooting.
## Component API
### ``
The main React component for the embedded editor.
```jsx
```
| Prop | Type | Default | Description |
| :---------------- | :-------- | :----------- | :----------------------------------------------- |
| `embedToken` | `string` | **Required** | The JWT generated by your backend. |
| `height` | `number` | `600` | Height of the editor in pixels. |
| `apiBaseUrl` | `string` | (Origin) | Base URL for API calls (use for self-hosted). |
| `showControls` | `boolean` | `true` | Show/hide the top toolbar (Undo, Redo, Publish). |
| `showFooter` | `boolean` | `true` | Show/hide the status bar (row count, search). |
| `showRowSettings` | `boolean` | `false` | Show gear icon for row-level settings. |
| `requestLabel` | `string` | "Request" | Custom label for the input columns section. |
| `responseLabel` | `string` | "Response" | Custom label for the output columns section. |
| `onPublish` | `func` | - | Callback fired after publishing `(e) => {}`. |
| `onError` | `func` | - | Callback fired on errors `(e) => {}`. |
### `createEmbedToken(options)`
Server-side helper to generate tokens.
```javascript
const { token } = await createEmbedToken({
apiKey: 'rb_live_...', // Required
ruleId: 'rule_123', // Required
expiresIn: 3600, // Optional (default: 1h)
baseUrl: '...', // Optional (for self-hosted)
})
```
## Programmatic Control (Refs)
You can interact with the component instance using a React Ref. This is useful for building external "Test" buttons or custom export flows.
```jsx
const ruleRef = useRef(null)
// ...
;
```
### Methods
- **`testRule(payload)`**: Runs a test against the current rule state. Highlights matching rows in the UI.
```javascript
const result = await ruleRef.current.testRule({ age: 25, type: 'standard' })
console.log(result.response) // The rule output
console.log(result.successIdxs) // Indices of matching rows
```
- **`clearTestResults()`**: Clears the visual highlighting from a previous test.
- **`getRule()`**: Returns the current JSON structure of the rule.
- **`isTestLoading()`**: Returns `true` if a test is currently executing.
## Branding & Customization
The embed **automatically inherits** branding from your Rulebricks organization settings (Colors, Fonts, Border Radius). No client-side config is needed for this.
### Manual Overrides (CSS)
If you need to override styles manually or create a dark mode workaround, target the `data-embed-container` attribute to ensure scoped styling.
```css
/* Override primary color */
[data-embed-container='true'] {
--rb-color-primary: #ff5733;
--rb-color-accent: #33ff57;
--rb-font-family: 'Inter', sans-serif;
--rb-border-radius: 8px;
}
/* Dark mode override example */
[data-embed-container='true'] .bg-white {
background-color: #1e1e1e !important;
color: #ffffff !important;
}
```
## Troubleshooting
| Error | Possible Cause | Solution |
| :---------------------------- | :--------------------------------------- | :---------------------------------------------- |
| **"No embed token provided"** | `embedToken` prop is null/undefined. | Ensure your fetch completes before rendering. |
| **HTTP 401 / Invalid Token** | Token expired or API Key revoked. | Generate a fresh token; check API key validity. |
| **HTTP 403 / Access Denied** | User/Key lacks permission for this rule. | Check Rulebricks permissions for that API key. |
| **Styles missing/broken** | CSS file not imported. | Add `import "@rulebricks/embedded/styles.css"`. |
| **Hydration Error (Next.js)** | SSR mismatch. | Use `next/dynamic` with `{ ssr: false }`. |
### Debugging
Pass an `onError` handler to the component to catch and log issues:
```jsx
console.error("Rulebricks Error:", e)} ... />
```
## Core Concepts & Security
/embedding-rulebricks/core-concepts-and-security
Understand the architecture, token flow, permission model, and security best practices for deploying Rulebricks Embedded in production.
# Core Concepts & Security
Understanding the architecture, permission model, and security best practices is crucial for a production deployment of Rulebricks Embedded.
## Architecture & Token Flow
Rulebricks Embedded uses a **token-based architecture** to ensure your API keys never reach the client.
1. **Client**: Your app requests access to a rule (e.g., when a user opens the editor).
2. **Server**: Your backend authenticates the user and uses your **Rulebricks API Key** to generate a short-lived, scoped `embedToken`.
3. **Rulebricks API**: Validates your key and the user's access, then returns a signed JWT.
4. **Client**: The `` component initializes with this token. All subsequent reads/writes use this token.
```mermaid
sequenceDiagram
participant User as Client App
participant Server as Your Backend
participant RB as Rulebricks API
User->>Server: Request Embed Token
Server->>RB: POST /embed/token (with API Key)
RB-->>Server: Return JWT Token
Server-->>User: Return JWT Token
User->>RB: (Direct API calls)
```
## API Keys & Permissions
Permissions in the embed are **inherited** from the API key used to generate the token.
### Hierarchy
- **Organization Admin Key**: Full access to all rules.
- **User Key**: Access only to rules owned by or shared with that specific user.
### Roles & Capabilities
The embed automatically adapts its UI based on the permissions granted:
| Permission | Effect in Embed |
| :-------------- | :------------------------------------------------------------------------------------------------ |
| **Read-Only** | User can view rules but cannot edit cells or add rows. Controls are hidden. |
| **Editor** | User can modify values and structure but cannot publish to production. |
| **Publisher** | User can edit and has access to the "Publish" button. |
| **Schema View** | Controls whether users see technical field names (`customer_id`) or descriptions (`Customer ID`). |
**Example**: To give a customer read-only access to their specific rule:
1. Use an API key associated with a read-only role (or the specific customer's key).
2. Generate a token for that rule ID.
3. The embed will render in "Read-Only" mode automatically.
## Security Best Practices
### 1. Never Expose API Keys
**Never** use your Rulebricks API key in client-side code. Always generate tokens on your server.
❌ **Bad:**
```javascript
// Client-side
fetch('https://rulebricks.com/api/embed/token', {
headers: { 'x-api-key': 'rb_live_...' }, // EXPOSED!
})
```
✅ **Good:**
```javascript
// Server-side
createEmbedToken({ apiKey: process.env.RULEBRICKS_API_KEY, ... })
```
### 2. Token Expiration
Set an appropriate expiration time (`expiresIn`). The default is 1 hour (3600s).
- **Short (15m)**: High security, requires refresh logic.
- **Long (8h)**: Better UX for internal tools.
### 3. Content Security Policy (CSP)
If your app uses CSP, allow connections to Rulebricks:
```html
```
## Installation & Setup
/embedding-rulebricks/installation-and-setup
Install the Rulebricks Embedded package, generate scoped embed tokens on your server, and render the embedded rule editor in your React app.
# Installation & Setup
Install the package using your preferred package manager:
```bash
npm install @rulebricks/embedded
# or
yarn add @rulebricks/embedded
# or
pnpm add @rulebricks/embedded
```
> **Note**: Requires React 18+.
## Quick Start
Getting up and running involves two steps: generating a secure token on your backend, and rendering the component on your frontend.
### 1. Server-Side: Generate an Embed Token
You must generate an access token on your server to keep your API key secure.
```javascript
// Node.js / Next.js API Route
export async function POST(request) {
// 1. Authenticate your user
const user = await getCurrentUser()
// 2. Generate the token
const { token } = await createEmbedToken({
apiKey: process.env.RULEBRICKS_API_KEY, // Your secret key
// Get this ID from the admin API, dashboard URL, or published URL slug
ruleId: 'abc123', // The rule to load
baseUrl: 'https://rulebricks.com', // Optional: for self-hosted
expiresIn: 3600, // Token validity in seconds
})
return Response.json({ token })
}
```
### 2. Client-Side: Render the Component
Fetch the token and pass it to the `` component. Don't forget the CSS import!
```jsx
import '@rulebricks/embedded/styles.css' // Required!
export default function RuleEditor({ ruleId }) {
const [token, setToken] = useState(null)
useEffect(() => {
fetch('/api/generate-token', { method: 'POST' })
.then((res) => res.json())
.then((data) => setToken(data.token))
}, [])
if (!token) return
Loading...
return (
console.log('Just Published:', e.rule)}
onError={(e) => console.error('Error:', e)}
/>
)
}
```
## Using your Rules
/getting-started/integration
Integrating Rulebricks into your workflows is easy, and there are a few ways you can do it. Learn how to integrate Rulebricks here.
# Using your Rules
Once you’ve finished configuring and testing your rule, it's time to publish your rule, and integrate it into your work.
You'll need to decide, given the technologies your business already uses, what the best way to connect it to your existing systems looks like.
We offer many ways to use your rules across different levels of technical expertise– you do not need to learn or employ all of them, merely pick out the one that works best in your situation.
## For Business Users
A great place to start as a less technically-involved Rulebricks user is to think about when you want your rule to actually run/be triggered. This will give you a bunch of relevant information to what's best for you at once– what data will be available, who is actually providing that data/where it's coming from, etcetera.
### Via Forms
If you want to use your rule to calculate something every once in a while, or help your users do so, forms are perfect for you. You don't actually have to build a form once you have your rule created– we generate the form for you using the logic in your rule! These forms can be embedded on your site and present your company's branding.
[Learn more about using forms →](/building-forms/creating-a-form)
### Via Zapier
This probably mainly works if you have heard of or are already familiar with Zapier, but we have a pretty powerful "Make Decision" action on Zapier that allows you to use Rules in lieu of Paths/Filters to perform complex comparisons/outcome determinations.

The action will guide you fully through the process of connecting your Rulebricks account to Zapier, connecting a rule to your Zap, and finally mapping data in your automation to send to the rule. If you ever run into issues with the action that do not seem clear, try deleting and re-creating the entire step, and if you’re still stuck, send us a quick email.
[Find us on Zapier →](https://zapier.com/apps/rulebricks/integrations)
### Developer Handoff
Given that you've created and published a rule– Rulebricks is incredibly quick for developers to integrate into existing applications, often taking less than half an hour to implement. What's more, they only need to set up each rule for you once– subsequent edits to the rule and changes to the decision table within will take effect seamlessly.
## For Developers
### Via API/SDK
A few API endpoints are made available for developers to effectively integrate a Rulebricks decision from anywhere. We also offer fully-featured SDK's in most programming languages that are dead-easy to install and use.
The particular endpoints that execute decision making assets are below, and all require the `x-api-key` header (with the API key found on your dashboard).
`/solve/[slug]` – **POST** your JSON request data to this endpoint to run your rule
`/flows/[id]` – **POST** your JSON request data to a Rule Flow you've created using this rule to run it as part of a larger sequence
Read more about these endpoints in our [API Reference](/api-reference), and use the tab there in the top right to switch to our SDK documentation in the language of your choice.
### Via Flows
Flows will still require use of our API/SDK noted above, but contain within them powerful abilities to chain rules, or connect them with databases or internal services.
One particularly powerful component of Flows is that flows allow you to leverage powerful caching mechanisms on any data fetching you're doing within– meaning you can create high-performance, low-latency Flows that still perform otherwise time consuming tasks like fetching data from an API or querying an SQL database.
Flows also allow you to explode lists and run rules against each object within.
[Learn more about Flow capabilities →](/integrating-rules/rule-flows)
## Requests & Responses
/getting-started/requests-and-responses
Requests and responses are the inputs and outputs to any rule in Rulebricks. Learn how to configure them here.
# Requests & Responses
## Inputs and outputs
Request & Response objects are, respectively, the inputs and outputs to any rule in Rulebricks. Specifically, they are any object in JSON notation, as previewed at the end of the last section.
Let’s look at those one more time:
### Request Object
```json
{
"industry": "Financial technology",
"company_size": 4400,
"location": {
"city": "San Francisco",
"state": "California",
"country": "United States"
},
"date_form_completed": "2022-11-23"
}
```
### Result Object
```json
{
"score": 90
}
```
## Inputs → "Request Columns"
**To begin configuring any rule in Rulebricks, you require a Request Object**.
Rulebricks uses the attributes available in the JSON object provided to initialize the columns of an empty decision table. Most applications will document examples of objects their platform outputs from various API endpoints and integrations on their website, but you're also free to create your own example objects/formats based on your needs for a particular decision point.
Each property in the JSON object corresponds with a column in your decision table, either as a "Request" column, within which you can define conditions against data, or a "Response" column within which you can define outcome data.
A fresh decision table initialized in Rulebricks using the request object a bit above might look like:
### **Request Columns**
| Industry `industry` | Company Size `company_size` | Location City `location.city` | Location State `location.state` | Location Country `location.country` | Date Form Completed `date_form_completed` |
| ------------------- | --------------------------- | ----------------------------- | ------------------------------- | ----------------------------------- | ----------------------------------------- |
| | | | | | |
Something that isn’t shown here is that the table also identifies what kind of data is in each column, determining whether it contains text, numbers, booleans, dates, or lists.
From here, you’re able to hide columns you’d like to ignore, rename columns to make information more meaningful, and set default values for information that is missing from future request objects.
## Outputs → "Response Columns"
**To receive any information from a rule, you require a Response Object.**
Much like the request object, Rulebricks will initialize columns to match any attributes you provide in this object. Unlike the request object, the response object doesn’t come from anywhere– you probably have to create it based on what you're trying to use rules to determine, and you’re free to add or remove any fields as they come to mind.
#### Functions
Response columns can uniquely also be "Function" type, which is a powerful type that essentially lets you create Excel-style formulas that can calculate using any of the input data or other output data. For example, if we override the price response column type in our running example to be a Function type column, we can simply type in something like `50 + company_size / 1.2` into cells in the price response columns.
## Build your first rule
/getting-started/start-building
Build your first rule in Rulebricks. Learn how to create a rule, add conditions, and test your rule here.
# Build your first rule
Let's get started building a rule. First, go to the [dashboard](https://rulebricks.com/dashboard). If this is your first time creating a rule, it will look something like this:

## Creating Your First Rule
### Create a new rule
Click the **Create Rule** button in the top right. You'll be presented with a dialog to configure your new rule.

### Select "From Scratch"
Choose **From Scratch** to build a rule from the ground up (just for this tutorial– we actually recommend using our wizard or a template if you'd like to skip to seeing what things look like in Rulebricks right away).

### Open the editor
Your rule will be created, and you'll be dropped into the **rule editor**. This is where you'll define your decision logic.

## Understanding the Rule Table
The **rule table** is the main area where you define your decision logic. It's split into two sections:
- **Request (Conditions)**: The left side contains the conditions that determine which row matches
- **Response (Results)**: The right side contains the values returned when a row matches
At the top of each column is the **column header** showing the column name and data type icon.
### Data Types
Rulebricks supports several types of data commonly used in decision-making:
Text is a string of characters.
```jsx
'Hello, world!'
'approved'
```
Example operations: Equals, contains, starts with, is valid email.
A boolean is a logical value: true or false.
```jsx
true
false
```
Example operations: Is true, is false.
A number is a numerical value—integer or floating-point.
```jsx
3.14
42 - 100
```
Example operations: Greater than, less than, equal to, within range.
A date is a calendar date and time in ISO 8601 format.
```jsx
'2024-01-15T10:30:00Z'
```
Example operations: Before, after, within last N days.
A list is a collection of items. Each item in the list can be of a different type.
```jsx
[1, 2, 3]
['apple', 'banana', 'cherry']
```
Example operations: Checking if a list contains a value, checking if a list is empty, checking length.
A function is a mathematical expression that computes a value.
Functions can only be used in the response section to compute values based on
request data.
```jsx
price * 1.08 // Add 8% tax
```
Want to see a full list of the operators supported for each type on Rulebricks? Check out our [Operator Index](https://rulebricks.com/operators).
## Setting Up Your Schema
The schema defines what data your rule accepts (request) and returns (response).
### Open the Schema Editor
Click on the **Schema** button to open the schema editor. This is where you define your input and output fields.
Simple mode gives you a clear view of your inputs. Click the colored type icons on each item to change its type.

You can also use **JSON mode** to paste a sample request object directly:

### Configure the Request Schema
Let's create a simple pricing rule. Set up your request schema with:
```json
{
"price": 40,
"date_purchased": "2024-01-15T10:30:00Z",
"gift": false
}
```
Click **Set Schema** to apply the changes.

### View Column Details
You can click on any column header to see and edit its details, including type and display name.

### Save the Schema
Click **Save** to apply your schema changes.

### Set Up the Response Schema
Configure what your rule returns. For our example:
```json
{
"result": true
}
```

## Adding Conditions and Results
Now let's add the actual decision logic to your rule.
### Add a condition
Click on a condition cell in the **price** column and add a condition. Let's check if the price is greater than 10.

### Set the result
Click on the result cell in the **result** column and set the value to **true**.

## Testing Your Rule
### Open the Test tab
Click on the **Test** tab in the sidebar to test your rule locally.

### Test with matching data
Enter a test request:
```json
{
"price": 20
}
```
Click **Test**. You should see the row highlight in green (conditions matched) and blue (result returned), with a 200 OK response.
### Test with non-matching data
Try a price that doesn't match:
```json
{
"price": 5
}
```

Since the price is not greater than 10, no row matches, and you'll get a 400 error.
## Adding a Catch-All Row
To handle cases where no specific condition matches, add a catch-all row.
### Add a new row
Click **Add Row** in the toolbar to add a blank row.

### Leave conditions as "Any"
The new row will have **Any** for all conditions, meaning it matches any input that doesn't match earlier rows.
### Set the catch-all result
Set the result to **false** for the catch-all row.
### Test again
Test with the price of 5 again:

Now the catch-all row matches, returning `result: false` with a 200 OK response.
## Final Rule State
Your completed rule should look like this:

**Row 1**: If price > 10 → result: true
**Row 2**: Catch-all → result: false
## How Row Matching Works
Rulebricks evaluates rows **top to bottom**:
1. For each row, all conditions in the request section are checked
2. If **all** conditions in a row pass, that row "matches"
3. The **first matching row** determines the response
4. If **no rows match**, an error is returned (unless you have a catch-all)
Cells highlighted in **green** indicate conditions that passed. Cells
highlighted in **red** indicate conditions that failed. Cells highlighted in
**blue** show the result that was returned.
---
That's it! You've built and tested your first rule. Continue to the next section to learn how to [publish your rule](/managing-rules/publishing-rules) and call it from your application.
## What is a rule?
/getting-started/what-is-a-rule
Understanding what a rule is in Rulebricks is the first step to building powerful automations. Learn more about rules here.
# What is a rule?
## A quick example
The fastest way to understand how rules work is by example.
Let’s say we’re working at a software company and have a few automations on Zapier that help us engage prospects that sign up on our website, follow up with them as potential customers, and add them to our CRM, and we’re looking to start scoring these prospects so we have a better understanding of which are more likely to convert and actually provide us with their business.
Our ideal customer could be anyone, but for the purposes of this example let’s say we’re looking specifically for startups in financial technology– these are the people most likely to buy our software. From some form responses and lead enrichment technologies, we have a bunch of data on each prospect, and we mainly need to look for industry and company size matches.
There might be some exceptions here, however– if someone from a particularly big company reaches out, we’ll probably at least want to talk with them to understand their needs, so we can score them highly. And our market might be local to the US, so we might like that as well.
From what we’ve written down so far, a decision table to score leads for our business might look like the following:
### **Conditions → Results**
| Industry | Company Size | HQ Location | Last Contact | | Score |
| ---------- | ------------ | ----------- | ------------- | --- | ----- |
| Fintech | < 500 | in US | Any | → | 100 |
| Fintech | > 2,000 | in US | < 30 days ago | → | 90 |
| Fintech | Any | not in US | < 30 days ago | → | 80 |
| Finance | Any | in US | < 30 days ago | → | 70 |
| Insurance | Any | in US | < 30 days ago | → | 50 |
| Government | Any | in US | < 30 days ago | → | 50 |
| Any | Any | in US | Any | → | 10 |
| Any | Any | Any | Any | → | 0 |
Unpacking this is quite straightforward.
Using our data on a prospect, we can identify if they meet all the comparisons in a row, looking at each row one at a time, from top to bottom. If we have a large US fintech that’s recently reached out, we’d find they meet all the conditions in the second row and stop there.
We then look at the results table for that particular row– any values in the second row of the results table are now our output and returned as results of this decision. The decision table above only has one column, so only the number 90 for the score is returned, but we could easily modify it to return other information along with this as well.
All the “Any” values at the bottom of the table are our catch-all, as we’ll want to return something if none of the rows match our data. In Rulebricks, you’re able to simply provide default values that help eliminate some of the need to explicitly define these rows everywhere.
#### Terminology
- **Comparisons**: The individual logical checks we’re making in our decision table. Each comparison is a single check that must be true for a row (condition) to be considered a match.
- **Conditions**: Each condition is a row in the table, i.e. a group of comparisons that must all be true for a row to be considered a match.
- **Request Data (Object)**: The incoming data for each decision we are using our rule to make using the left side of the table, i.e. the data that is being compared to the conditions.
- **Results/Response Data (Object)**: The outgoing data on the right side of the table, i.e. the data that is returned from our rule when a condition row is considered a match.
Some terminology is used interchangeably, but these are the most common terms you’ll see when working on Rulebricks.
## What this actually looks like
Decision tables are fairly simple conceptually, and can be straightforward enough to set up on Rulebricks, but how do they interact with real world data? The decision above requires a **Request Object** to configure, and return a **Result Object**, further discussed in the next section, and previewed below.
#### Request Object
```json
{
"industry": "Financial technology",
"company_size": 4400,
"location": {
"city": "San Francisco",
"state": "California",
"country": "United States"
},
"date_form_completed": "2022-11-23"
}
```
#### Result Object
```json
{
"score": 90
}
```
If you look closely, you’ll notice some details that make this decision possible that aren’t obvious– the data is not *exactly* what we represented it in the decision table, and the date is just text, so it’s not immediately clear how to set up a comparison for that.
Here we can start seeing a few of the logistics around defining rules that Rulebricks can make particularly easy.
## Flow Functions
/integrating-rules/flow-capabilities
Flows are a powerful way to chain together multiple rules, alongside calls to external services, and structure complex decision-making workflows. Explore their latest capabilities here.
# Flow Functions
While your configurable rules are undoubtedly the most valuable pieces Flows expose, we offer several built-in functions to help your team collect data from external sources and run logic conditionally.
We call these "flow functions"– special operations you can use in your flows to perform various actions beyond rule execution. Below are all the available flow function node types– but do try our application or reach out to get information on our latest capabilities.
## Continue If
}
name="Continue If"
description="Evaluate if an input property is true/truthy and only proceed with specific data if so."
/>

Proceed based on one of four selectable conditions:
- If a specific property is **"truthy"** (not empty, non-zero, non-null)
- If a specific property is exactly **`true`**
- If a specific property is exactly **`false`**
- If a specific property is **"falsy"** (empty list, zero, null, or false)
If the condition is met, the flow continues along the exiting edges. Use two "Continue If" nodes to model if/else logic.
All data you want to proceed with after a Continue If node must be wired in as
inputs.
## Result Object
}
name="Result Object"
description="Nest data in a result object within a specific key to better organize the final output of your flow."
/>

The final output of your flow. It collects results from all preceding nodes and returns them as the flow's response.
- You can use multiple Result Object nodes with unique keys
- **Required**: Every flow must end with at least one Result Object node
## Lookup Table
}
name="Lookup Table"
description="Map an input 'key' to any output 'value' based on a table of key & value pairs."
/>

Look up a value in a table and return a corresponding value if an exact match is found. Works well with Continue If nodes by returning true/false based on lookup results.
## Run Code
}
name="Run Code"
description="Execute custom JavaScript to perform operations like computing data, making API calls, or integrating with external services."
/>

Run custom JavaScript code using data from your flow. Useful for:
- Making calls to external services
- Performing complex calculations
- Custom string formatting or data transformation
Available libraries: `fetch`, `moment`, `lodash`, and more.
Access connected values via the `inputs` dictionary and return values via the `outputs` dictionary.
```javascript
// Example: Calculate discount
const total = inputs.subtotal
const discount = total > 100 ? total * 0.1 : 0
outputs['discount'] = discount
outputs['final_total'] = total - discount
```
## SOAP Request
}
name="SOAP Request"
description="Connect to SOAP/XML web services using WSDL to call operations on enterprise systems."
/>
Connect to SOAP/XML web services to integrate with legacy enterprise systems. Upload a WSDL file to auto-discover available operations.
Features:
- WSDL-based service discovery
- Support for complex XML request/response structures
- Built-in authentication options
## Database Query
}
name="Database Query"
description="Query a PostgreSQL database to fetch data from an external resource and use it in this flow."
/>

Query a PostgreSQL, MSSQL, or MySQL database, and return results. Variables from your flow can be used in the query.
Features:
- Use `rows` with a For Each node or send directly to Result Object
- Built-in 1-minute caching for performance
## API Request
}
name="API Request"
description="Make an HTTP request to any API to fetch & send data from/to external resources."
/>

Make HTTP requests to external APIs. Variables from your flow can be used in the URL, headers, and body.
Features:
- GET, POST, PUT, DELETE methods
- Custom headers and authentication
- Built-in 1-minute caching, customizable cache key
## For Each
}
name="For Each"
description="Perform following operations on each available item in a non-empty list of values/objects, and return a list of results."
/>

Iterate over a list of items and apply a rule to each one. Perfect for:
- Transforming collections of objects
- Processing lists of orders, users, or products
- Batch operations
For Each nodes must culminate in a Result Object node. Values inside the loop
cannot connect to parts of the flow outside that loop (indicated by different
edge colors).
## AI Inference
}
name="AI Inference"
description="Intelligently parse out structured properties from text using AI models to evaluate rules against."
/>

Extract structured data from text using OpenAI. Useful for:
- Parsing emails and reports
- Extracting specific information from documents
- NLP preprocessing before rule execution
To use:
1. Connect a text input
2. Define target labels to extract
3. Click "Run Inference"
## Vault
}
name="Vault"
description="Connect to secure key vaults to retrieve secrets at runtime without exposing credentials."
/>

Securely retrieve sensitive information from cloud secrets managers (AWS, GCP, Azure).
Security features:
- Secrets never appear in clear text in the flow
- Cannot be logged or returned in output
- Only accessible within Run Code nodes
## Context Operation
}
name="Context Operation"
description="Read, update, or delete context data. Use for stateful context data operations within flows."
/>
Interact with [Rulebricks Contexts](/contexts) directly from your flow. Perfect for:
- Reading entity data to use in rule evaluation
- Updating context properties based on flow outcomes
- Deleting or archiving context records
Context Operations require a configured Context in your workspace. See the
[Contexts documentation](/contexts) for setup instructions.
## Send Notification
}
name="Send Notification"
description="Send multi-channel alerts via Email, Slack, Discord, or PagerDuty based on flow data."
/>
Trigger notifications to your team or external systems based on flow outcomes. Supports multiple channels:
- **Email**: Send templated emails with flow data
- **Slack**: Post messages to channels or DMs
- **Discord**: Send webhook messages to Discord servers
- **PagerDuty**: Create incidents for on-call alerting
Configure notification channels in your workspace settings before using this
node.
## Flows in Rulebricks
/integrating-rules/rule-flows
Flows in Rulebricks are a powerful way to chain together multiple rules, alongside calls to external services, and structure complex decision-making workflows. Learn how to create a Rule Flow here.
# Flows in Rulebricks

Flows are a powerful way to chain together multiple rules, alongside calls to external services, and structure complex decision-making workflows.
These are particularly useful when you have a series of decisions that need to be made in a specific order, or when you'd like to separate logic into multiple rules for easier maintenance of your decision-making logic.
Flows are built with external data and massive scale in mind, and can help you
easily cache data consumed from external sources within to support uniquely
low latencies and high throughput.
## Flow Reactivity
Unique to Rulebricks, the Rulebricks Flow editor is **reactive**— as you make changes to rule input fields, the editor automatically visualizes your data flow in real-time. This makes it incredibly easy to see how your flow behaves _as you build it_ without repeated end-to-end build/test cycles.
Reactivity applies to all sequences of nodes until certain execution or query nodes are reached. These nodes pause execution until you manually click the run button, preventing accidental spam to external services.
While this feature exists to help you model your flow while understanding what is happening to the actual data as it moves through, it is not a perfect guarantee of how your Flow will behave _end-to-end_– for that, simply use the "Try" or "Test" tabs, available in the sidebar.
## Creating a Rule Flow
### Navigate to the Flows tab
Open the Rulebricks dashboard and click the **Flows** tab.
### Create a new flow
Click the **Create Flow** button to create a new Rule Flow.

You must have at least one published rule in your workspace to use as a
starting point for your flow.
### Build your flow
You'll be taken to the Flow editor where you can:
- Drag and drop published rules from the sidebar onto the canvas
- Add flow functions (code execution, API calls, etc.)
- Connect nodes together to define the execution order

### Connect your nodes
Drag from an output handle of one node to the input of another to connect them. Data flows through the connections.

## Team Templates
/managing-rules/creating-templates
Create reusable rule templates for your team. Templates share proven schemas and decision patterns across your organization.
# Team Templates
Templates let you capture a well-designed rule and make it available for your whole team to reuse, or for customers to use as a starting point.
When someone creates a new rule from a template, they get the schema, sample conditions, settings, and test cases, ready for them to customize.
While exported RBM files do save and contain the histories of exported decision assets, users importing them as Team Templates will not receive these histories.
## Creating a Template
### Build the rule you want to share
Create a rule with the schema, conditions, and settings you want others to start from. Include test cases if you want them copied to new rules.
### Save as template
Navigate to **Team → Templates** and click **Create Template**, or use the **Create Template** button in the rule editor.

You can also upload `.rbm` files directly to your templates.

## Using a Template
When creating a new rule, click the **Templates** tab in the create dialog. Team Templates should appear by default. If you're not seeing them, it's possible the template has been assigned to a specific User Group, and you may not be part of it.

## Managing Templates
- **Administrators** and **Developers** can create and delete templates
- All team members can view and use templates
- Templates can't be edited—create a new version with a clear name like `"Credit Check - v2"`
Templates can have tenancy– if a template is assigned to a particular user
group, only users belonging to that group will see it. Developers (usually
unassigned to any user group) will only see templates that do not have any
user group.
## Or, Duplicate
If templates feel heavyweight for your needs, users can always find an existing rule and click **Duplicate**. This works well for quick iterations or one-off copies.
## Exporting Rules & Flows
/managing-rules/exporting-rules
Export rules and flows as .rbm files for backup, version control, or sharing across workspaces.
# Exporting Rules & Flows
Export rules and flows as `.rbm` files to back them up, share them across workspaces, or store them in version control.
## Exporting
### Open the rule or flow
Navigate to the asset you want to export.
### Click Export

### Name your export

### Review dependencies
Exports intelligently bundle all assets related to the item you're exporting. Exporting Flows also exports the rules within. Exporting rules also exports any values those rules reference.
This ensures you won't have to think about dependencies while moving around assets- all relevant data is self-contained within a single file.

### Download
Click **Export** to download the `.rbm` file.
## Importing
Click **Create → Import** in the dashboard and upload an `.rbm` file.
We offer two conflict resolution schemes, that help address what happens if you're pulling in data you might already have a local copy of.

## What's Included
- **Rules** export with all conditions, results, schema, referenced dynamic values, settings, test cases, and publication histories.
- **Flows** export the complete flow definition plus all embedded rules.
We do not obfuscate our schemas in exported files and encourage users to review how their assets are stored. RBM files are stored as large amounts of JSON, but they are structured in a way your team may be able to build custom tools directly against them!
## Publishing Rules
/managing-rules/publishing-rules
Publishing a rule in Rulebricks makes it available for use in your applications. Learn how to publish, version, and compare rules.
# Publishing Rules
Publishing makes your rule available via the API, in Forms, and in Rule Flows. Every publish creates a new version—you can target specific versions, compare changes, and roll back if needed.
## Create a new version
### Open the publish menu
Click the **Publish** button in the top right corner of the rule editor.

### Publish your rule
You'll see a history of prior versions. Click **Publish** to create a new version.

You can click on the colored tags in this menu ("Conditions", "Request
Schema", etc.) to see a summary of what changed between versions.
## Versioning
Each publish creates a numbered version (v1, v2, etc.). Target specific versions by appending the number to the rule slug:
- `hlDPsJMVGQ` → latest version
- `hlDPsJMVGQ/2` → version 2 specifically
This lets you build highly stable and customizable deployment workflows around your rules while developing future versions.
The clock icon next to each version lets you **roll back**. This is
destructive—versions between your current state and the rollback target are
permanently lost as API targets.
## Comparing Versions
Before publishing, compare your working changes against any previous version to see exactly what's different.
Click **Compare Versions** in the rule editor header:

The comparison view shows your working changes side-by-side with the selected version. Added rows, removed rows, and modified conditions are highlighted:

Use this to review changes before publishing, or to understand what changed between versions when investigating unexpected behavior.
New versions take effect roughly 1 minute after publishing while caches
update.
## Decision Logs
/managing-rules/rule-logging
Decision logs record every rule execution with full request/response data. Search with DDQL to find specific decisions.
# Decision Logs
Every rule execution is logged with the full request, response, and which conditions matched. Use logs to debug issues, audit decisions, and understand how your rules behave in production.
Logs are retained for up to 90 days on most plans and appear in the dashboard
within seconds of execution.
## Viewing Logs
Navigate to the **Logs** tab in the dashboard.

Each entry shows:
- Request and response payloads
- Which conditions matched or failed
- HTTP status, IP address, timestamp, and duration
Filter by rule, status code, or date range to narrow down what you're looking for.
## Searching with DDQL
Decision Data Query Language lets you search by the actual data in your requests and responses.
For example, if I wanted to pull rule executions for a particular `application_id`, I might do the following:

### Examples
```
credit_score >= 700 AND approval_decision = "denied"
customer_id = "CUST-12345"
amount > 10000 AND risk_score < 0.2
tier:gold OR tier:platinum
```
### Operators
| Operator | Description | Example |
| ----------------- | ---------------------------- | ------------------ |
| `=` | Exact match | `status=approved` |
| `!=` | Not equals | `status!=rejected` |
| `:` | Contains (strings) or equals | `name:Smith` |
| `<` `<=` `>` `>=` | Comparisons | `score>=700` |
Combine with `AND` and `OR`. Use parentheses for precedence:
```
(tier = "gold" AND amount > 100) OR tier = "platinum"
```
Quotes optional unless value contains spaces:
```
status = approved
city = "New York"
```
Integers and decimals:
```
score >= 85.5
quantity = 0
```
```
verified = true
flagged = false
```
Type a word to search anywhere in request or response:
```
timeout
error
```
Field names are case-sensitive and must match your schema. Queries search both request and response payloads automatically.
## Log Details
Click any entry to see the full request/response JSON, detailed condition evaluation, and timing data.
## Testing Rules
/managing-rules/testing-rules
Test your rules before publishing with Try mode for quick checks and Suite mode for regression testing.
# Testing Rules
Test your rules before they hit production. Rulebricks gives you two modes: **Try** for quick single-request testing, and **Suite** for regression testing across multiple scenarios.
## Try Mode
Open the **Test** tab in the rule editor and select **Try**.


Paste a request object and click the orange button. The decision table lights up:
- **Green cells** — Conditions that matched
- **Red cells** — Conditions that didn't match
- **Blue/Yellow cells** — The results returned
This visual feedback shows exactly which row matched and why.
If you get an error, your rule doesn't handle that input. Check for missing
conditions or add a catch-all row at the bottom.
## Suite Mode
Switch to **Test Suite** mode to run multiple test cases at once.

Build your suite by saving tests from Try mode. When a test produces the expected result, save it. Mark tests as **Critical** if they should block publishing when they fail.
### Rapid Test Generation
We find writing tests boring, and we thought you might too– a powerful AI feature here allows you to create many tests very quickly given one or two examples. Just build and name one test case in the Suite, and click "Generate Tests" to instantly get coverage over your entire rule.
For large test suites, we offer a simple API to bulk upload test cases
programmatically.
## Continuous Testing
Enable **Continuous Testing** in rule settings to run your test suite automatically before every publish.
When enabled, you'll see a test score next to every published version—the percentage of tests that passed. Critical tests prevent publishing if they fail; non-critical tests report results but don't block.
## Objects in Rulebricks
/objects
Objects are reusable schema definitions that provide field suggestions when building rules and contexts. Define once, use everywhere.
# Objects in Rulebricks
You've got a customer object with `tier`, `credit_score`, and `lifetime_value`. You'll use those fields in a dozen rules. Do you really want to type them out each time?
Objects are team-level schema definitions that provide field suggestions when you create rules or contexts. Define your data structure once, and every rule you build can pull from it.

Rulebricks Admins can find this tab within the **Team** tab on your dashboard.
## Data Clarity
### Field suggestions
When building a rule, select an Object and its fields appear in the schema editor. No retyping, no typos.

### Automatic Dynamic Values
If your Object uses JSON Schema with `enum` fields, Rulebricks extracts those values and creates dropdown options in your rule conditions.

### Consistency
When everyone builds rules from the same Objects, field names stay consistent across your workspace. This way, you'll never run into issues where rule one may have a schema like:
```json
{
"customer_id": "cust_12345",
"order_total": 299.99,
"is_priority": true
}
```
And rule two, designed elsewhere, uses:
```json
{
"customerId": "cust_12345",
"total": 299.99,
"priority": true
}
```
## Accepted Formats
Objects can be defined using both JSON Schemas, or just an example JSON object. If you're planning to use our objects feature, it's worth noting using JSON Schemas has substantial benefits across our platform that may not be immediately obvious.
Even if you don't have a unified JSON schema anywhere, we make it really easy to make one using our "Smart Import" tool.
**JSON Schema** — Full schema definitions with types, validation rules, and enums
```json
{
"type": "object",
"properties": {
"customer_tier": {
"type": "string",
"enum": ["bronze", "silver", "gold", "platinum"]
},
"credit_score": {
"type": "number",
"minimum": 300,
"maximum": 850
}
}
}
```
**JSON Object** — Plain example objects when you just want field suggestions without full validation
```json
{
"customer_id": "cust_12345",
"order_total": 299.99,
"is_priority": true
}
```
Rulebricks will automatically detect which format you're using.
## Objects vs. Contexts
Two features that may initially appear somewhat similar on our platform are _Objects_ and _Contexts_.
The _Objects_ described here are designed for bringing the shape and fields of business objects that may live elsewhere into Rulebricks for consistency.
The _Contexts_ feature, discussed later in this documentation, while able to create a view of your data relevant for decision making in a similar way, is designed exclusively for decisions where data arrives over time. Contexts are stateful and have lifespans associated with them.
## Creating Objects
/objects/creating-objects
Three ways to create Objects in Rulebricks—manual creation, AI-powered Smart Import, or pre-built templates.
# Creating Objects
Go to **Team → Objects** and click **Add Object**.

Enter a name and paste or write your JSON. Rulebricks detects whether it's a JSON Schema or plain JSON Object automatically.
Good for quick definitions when you know exactly what structure you need.
Let AI generate a schema from your existing documentation.
Click **Smart Import** and provide:
- **Name and description** of what you're modeling
- **Attachments** — PDFs, spreadsheets, SQL DDL files, images, or URLs
- **Notes** — Additional context or constraints
Click **Generate** and Rulebricks creates a complete JSON Schema.
**Supported files:** PDF, Excel, CSV, Word, JSON, SQL/DDL, images (with OCR), and web URLs.
Good for converting existing specs, database schemas, or documentation without manual translation.
Pre-built schemas for common use cases.

Click **Use Template**, browse categories or search, preview the schema, and click **Add to Workspace**. Templates are pretty good for getting started quickly with industry-standard data structures.
## Using Objects in Rules
When creating a rule, click **From Object** to pull fields from an existing Object:

Select the fields you need and they're added to your rule's schema—correctly typed and named.
## Automatic Dynamic Values
When you save a JSON Schema with `enum` fields, Rulebricks extracts those values and creates Dynamic Values:

These appear as dropdown options in your rule conditions, so instead of typing `"gold"` you select it from a list. Fewer typos, faster rule building.
Objects can be used to inform rules and contexts. Update the Object and you
have a single source of truth for that data structure.
## Architecture & Operations Guide
/private-deployment/architecture
Explains the Rulebricks Helm chart architecture, component interactions, migration flows, and operational considerations.
# Architecture & Operations Guide
This document explains the Rulebricks Helm chart architecture, component interactions, migration flows, and operational considerations, so you can better configure your deployment.
### Component Overview
```mermaid
graph TB
subgraph Internet
Users[Users/API Clients]
end
subgraph Kubernetes["Kubernetes Cluster"]
subgraph Ingress["Ingress Layer"]
Traefik[Traefik LoadBalancer]
end
subgraph Core["Rulebricks Core"]
App[Rulebricks App]
HPS[HPS Gateway]
Workers[HPS Workers]
Redis["Valkey Cache (Redis-compatible)"]
end
subgraph Data["Data Layer"]
Kafka[Kafka]
Postgres[(PostgreSQL)]
Kong[Kong API Gateway]
end
subgraph Supabase["Supabase Stack"]
Auth[GoTrue Auth]
Rest[PostgREST]
Realtime[Realtime]
Storage[Storage API]
end
subgraph Logs["Decision Log Pipeline"]
Vector[Vector]
ClickHouse[ClickHouse]
end
subgraph Observability["Operational Observability"]
ClickStack[ClickStack / HyperDX]
end
subgraph Support["Supporting Services"]
CertManager[cert-manager]
KEDA[KEDA]
ExternalDNS[external-dns]
Prometheus[Prometheus]
end
end
subgraph External["External Services"]
LetsEncrypt[Let's Encrypt]
DNS[DNS Provider]
ObjectStorage["Object Storage (S3/Azure Blob/GCS)"]
end
Users --> Traefik
Traefik --> App
Traefik --> HPS
Traefik --> Kong
App --> Redis
HPS --> Kafka
HPS --> Workers
Workers --> Kafka
Workers --> Redis
Kong --> Auth
Kong --> Rest
Kong --> Realtime
Kong --> Storage
Auth --> Postgres
Rest --> Postgres
Kafka --> Vector
Vector --> ObjectStorage
ClickHouse --> ObjectStorage
App --> ClickHouse
App --> ClickStack
HPS --> ClickStack
CertManager --> LetsEncrypt
ExternalDNS --> DNS
KEDA --> Workers
```
### Request Flow
```mermaid
sequenceDiagram
participant Client
participant Traefik
participant HPS
participant Kafka
participant Worker
participant Redis
Client->>Traefik: POST /api/v1/solve/{rule}
Traefik->>HPS: Route to HPS Gateway
HPS->>Kafka: Publish solve request
HPS->>Client: 202 Accepted (async)
Kafka->>Worker: Consume solve request
Worker->>Redis: Fetch rule definition
Worker->>Worker: Execute rule
Worker->>Kafka: Publish result
Worker->>Kafka: Publish decision log
```
### Component Responsibilities
| Component | Purpose | Scaling |
| :-------------- | :--------------------------------------- | :------------------------------------ |
| **Traefik** | Ingress, TLS termination, routing | HPA (1-4 replicas default) |
| **App** | Web UI, rule editor, management API | Single replica |
| **HPS Gateway** | API gateway for rule execution | KEDA-scaled (3-8 replicas default) |
| **HPS Workers** | Rule execution engines | KEDA-scaled Deployment |
| **Valkey (Redis-compatible cache)** | Rule cache, session storage | Single replica with persistence |
| **Kafka** | Message queue, event streaming | KRaft mode, single node default |
| **PostgreSQL** | Persistent data storage | Single replica with PVC |
| **Vector** | Decision log aggregation and forwarding | Static replicas |
| **ClickHouse** | Decision log queries and telemetry store | Single replica with persistence |
| **ClickStack** | Traces, operational logs, and HyperDX UI | Single replica default |
| **Prometheus** | Metrics collection | Single replica, optional remote write |
---
## Decision Log Pipeline
Every rule execution emits a structured decision log. HPS API and worker pods produce entries to the Kafka `logs` topic (`com.rulebricks.logs` with the default topic prefix); Vector archives them to object storage as NDJSON; ClickHouse queries the archive on demand for the dashboard:
```mermaid
flowchart LR
HPS["HPS API / worker pods"] -->|produce| LogsTopic["Kafka logs topic"]
LogsTopic -->|consume| Vector[Vector]
Vector -->|"NDJSON archive (hourly partitions)"| Bucket["Object Storage (global.storage)"]
Bucket -->|query on demand| ClickHouse[ClickHouse]
ClickHouse --> Dashboard[Rulebricks Dashboard]
```
The full walkthrough, values, and custom sink examples live in [Decision Logs](/private-deployment/decision-logs). Database backups travel to the same bucket under a separate key prefix; see [Storage & Backups](/private-deployment/storage-and-backups).
---
## Database Migrations
The chart includes two migration job variants that run automatically based on your configuration.
### Migration Decision Tree
```mermaid
flowchart TD
A[Helm Install/Upgrade] --> B{supabase.enabled?}
B -->|true| C[Self-Hosted Migration Job]
B -->|false| D{accessToken provided?}
D -->|Yes| E[Managed Supabase Setup Job]
D -->|No| F[No migration job runs]
C --> G[Wait for Postgres ready]
G --> H[Extract migrations from app image]
H --> I[Apply SQL migrations via psql]
I --> J[Track in schema_migrations table]
E --> K[Install Supabase CLI]
K --> L[Authenticate with access token]
L --> M[Link to project]
M --> N[Push config.toml]
N --> O[Run db push]
O --> P[Enable SSL enforcement]
```
### Self-Hosted Migration Job
**Triggered when:** `supabase.enabled: true`
The job extracts SQL migrations from the Rulebricks app image, waits for PostgreSQL to become ready, and applies them in order. Applied migrations are tracked in a `schema_migrations` table so re-runs skip what's already done. Jobs run as Helm post-install/post-upgrade hooks and clean themselves up automatically.
### Managed Supabase Setup Job
**Triggered when:** `supabase.enabled: false` AND `global.supabase.accessToken` is provided
The job authenticates with your access token via the Supabase CLI, links to your project, pushes configuration (domain, SMTP, auth settings, email templates), applies the schema with `supabase db push`, and enables SSL enforcement.
If a migration job fails, see [Troubleshooting](/private-deployment/troubleshooting#migration-jobs) for diagnostics.
---
## DNS and TLS Setup
### DNS Record Requirements
| Scenario | Records Needed |
| :------------------- | :------------------------------ |
| Self-hosted Supabase | `{domain}`, `supabase.{domain}` |
| Managed Supabase | `{domain}` only |
---
## Scaling Considerations
| Component | Scaling Method | Trigger |
| :---------- | :------------- | :------------------------------- |
| Traefik | HPA | CPU utilization |
| HPS Gateway | KEDA | `solution-response` lag, CPU |
| HPS Workers | KEDA | Kafka lag, CPU |
| Vector | Manual | Log volume |
Workers run as a Deployment scaled by KEDA, so scale-out creates pods in parallel and delivers burst capacity in seconds. Worker concurrency is bounded by the partition count of the `solution` Kafka topic; the full sizing model and the values that control it live in [Performance & Scaling](/private-deployment/performance).
For diagnostics across all components, see [Troubleshooting](/private-deployment/troubleshooting).
## Provisioning Users
/private-deployment/authentication
Understand controls to automatically configure Rulebricks app access, roles, and tenancy for users entering Rulebricks via an external Identity Provider.
# Provisioning Users
This section explains how Rulebricks maps incoming SSO identities onto its internal authorization model, and how to configure that mapping from the **Team → SSO** tab. It is written for workspace administrators running a self-hosted or white-labeled Rulebricks deployment.
If you cannot provision an SSO application for Rulebricks — for example because Rulebricks lives inside a private network behind an existing identity-aware proxy — see [Token Passthrough](/private-deployment/authentication/token-passthrough) for the alternative that delegates authentication to your upstream proxy.
## What the SSO Team Tab Does
The SSO Team tab connects incoming identity claims from your IdP to four dimensions of Rulebricks authorization:
- **Access** — who is allowed to enter the application at all
- **Tenants** — which tenancy boundaries a user can see
- **Roles** — what permissions the user has once inside
- **Profile** — how the user's name and avatar are populated
Each dimension is configured independently. The full reference for every field lives in [Claim Mapping](/private-deployment/authentication/claim-mapping).
## How the SSO Model Works
Rulebricks uses a parent-child team model.
- The first admitted SSO user becomes the **root workspace administrator**.
- That user has no parent and is the single built-in administrator for the workspace.
- Every later admitted SSO user is adopted under that administrator as a child user.
- Child users receive:
- a Rulebricks role
- optional tenant assignments
- optional profile mappings
This means the SSO layer does not create a separate authorization model. It resolves users into the same internal team structure that Rulebricks already uses everywhere else.
## The One Administrator Rule
Rulebricks maintains exactly one built-in administrator per workspace:
- The first admitted user is the workspace administrator.
- The built-in `Administrator` role is **not assignable** through SSO role mapping.
- Later users can be assigned system roles like `Developer` or `Editor`, or custom roles created in Rulebricks.
This prevents claim mapping from accidentally creating multiple built-in administrators. If you need a class of trusted cross-tenant users, use the [admin tenant value](/private-deployment/authentication/claim-mapping#admin-tenant-value) instead, which affects tenant visibility without granting administrator status.
## What Happens When Mapping Fails
Rulebricks treats failed mapping safely by default.
Examples of failed mapping include:
- access rules block the user
- role mapping produces no valid role
- tenant mapping is enabled but no tenant claim is present
- the system cannot safely determine how the user should be adopted into the workspace
In these cases the user is denied access and sent to the access-unavailable screen.
There are two intentional exceptions:
- If a fallback role is enabled, an unmatched role resolves into that role instead of producing a denial.
- If the tenant claim refers to a tenant that does not exist yet, Rulebricks auto-creates the tenant.
Tenant auto-creation is always on. Administrators do not need to pre-create
every tenant value — newly seen tenants are created automatically on first
login.
## Recommended Configuration Pattern
For most teams, a good configuration sequence is:
### Admit the first user
Let the first SSO user enter and become the workspace administrator.
### Create custom roles
If you plan to use custom roles, create them first in the Team UI so they are available when you configure role mappings.
### Configure Access
Configure `Access` rules if only certain SSO users should be admitted at all.
### Configure Tenants
Configure `Tenants` so normal users land in the correct tenant automatically.
### Set the Admin tenant value
Set an `Admin tenant value` if you need specific users to see across all tenants.
### Configure Roles
Configure `Roles` so users get the correct Rulebricks permissions. Enable a fallback role if you want unmatched users to land on a safe default rather than being denied.
### Configure Profile
Optionally configure `Profile` so user display names and avatars are populated from SSO.
## Access Levels at a Glance
| Dimension | Controlled by | Possible outcomes |
| :--------------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------- |
| **Workspace access** | `Access` section | Allowed into the application, or denied before any workspace mapping |
| **Tenant visibility** | `Tenants` section | Assigned to one or more tenants, or assigned to no tenants via the admin tenant value (cross-tenant visibility) |
| **Role permissions** | `Roles` section | Mapped to a system role, mapped to a custom role, assigned the fallback role if configured, or denied if no role mapping hits |
| **Profile population** | `Profile` section | Name and avatar remain unchanged, or are automatically populated from SSO claims |
## Where to Next
- [Claim Mapping](/private-deployment/authentication/claim-mapping) — the full reference for the `Access`, `Tenants`, `Roles`, and `Profile` sections of the Team tab.
- [Token Passthrough](/private-deployment/authentication/token-passthrough) — for deployments that cannot provision an SSO application for Rulebricks and instead want to delegate authentication to an upstream proxy.
## Claim Mapping
/private-deployment/authentication/claim-mapping
Reference for the Access, Tenants, Roles, and Profile configuration sections in the Rulebricks SSO Team tab.
# Claim Mapping
This is the reference page for the four configuration sections in the **Team → SSO** tab. Each section maps one kind of identity claim onto a dimension of the Rulebricks authorization model. For the conceptual background behind this model — including the one-administrator rule and the parent-child team structure — see the [SSO Overview](/private-deployment/authentication).
The tab is organized into four sections:
- [Access](#access) — controls who is allowed into the application
- [Tenants](#tenants) — maps users into tenancy boundaries
- [Roles](#roles) — maps identity claim values to Rulebricks roles
- [Profile](#profile) — populates a user's profile from identity claims
Together, these sections determine how SSO users are admitted and what they are allowed to see and do after they sign in.
## Access
The `Access` section controls whether every SSO user is evaluated for workspace mapping, or whether only a subset of users can enter the application at all.
### When Access Rules Are Off
If the access toggle is off:
- Every SSO user is allowed to continue into Rulebricks.
- They are then evaluated by the `Tenants`, `Roles`, and `Profile` sections.
- Users who cannot be mapped safely may still be denied later, depending on the configuration.
### When Access Rules Are On
If the access toggle is on:
- Rulebricks reads a configured identity claim field.
- The claim must match one of the configured allowed values.
- If it does not match, the user is denied before any workspace role or tenant assignment happens.
This is useful when only a subset of your SSO population should be allowed to enter the application.
## Tenants
The `Tenants` section maps users into tenancy boundaries.
In Rulebricks, tenants are implemented through user-group assignments behind
the scenes. The Team UI uses the term `Tenants` for clarity and consistency.
### Standard Tenant Mapping
When tenant mapping is enabled:
- Rulebricks reads the configured tenant claim field.
- The resulting tenant value is used to assign the user into the matching tenant.
- If the tenant does not exist yet, Rulebricks automatically creates it.
Tenant auto-creation is always enabled and enforced. This means:
- Administrators do not need to pre-create every tenant value.
- Newly seen tenant values can be created automatically on first login.
### Tenant Assignment and Visibility
Tenant assignment controls what scoped data a user can see.
- Users assigned to one or more tenants see the data available to those tenants.
- Users assigned to no tenants can see across all tenants.
That second case is important for the administrative tenancy behavior described below.
### Admin Tenant Value
The tenant section includes an `Admin tenant value`.
If a user's tenant claim matches this value:
- The user is intentionally not assigned to any tenant.
- They can see across all tenants.
- Their Rulebricks role is still determined separately by the `Roles` section.
Admin tenant mapping does **not** automatically make a user a built-in
Rulebricks administrator. It only affects tenant visibility. Use it when you
want a class of trusted cross-tenant users, but still want role-based
permissions to be controlled separately.
## Roles
The `Roles` section maps identity claim values to Rulebricks roles.
### What Can Be Mapped
Users can be mapped to:
- `Developer`
- `Editor`
- Custom Rulebricks roles that you create in the Team UI
Users cannot be mapped to the built-in `Administrator` role. See [The One Administrator Rule](/private-deployment/authentication#the-one-administrator-rule) for why.
### Role Mapping Behavior
Role mapping works by:
1. Selecting an identity claim field for roles.
2. Defining claim value → role mappings.
3. Assigning the matching Rulebricks role when a user signs in.
### Fallback Role
The Roles section can optionally define a fallback role.
If fallback is disabled:
- Users whose role claim does not match any mapping are denied access.
If fallback is enabled:
- Unmatched users are assigned to the configured fallback role instead of being denied.
This gives administrators a controlled way to admit users whose role claims are incomplete or unexpected.
## Profile
The `Profile` section maps identity claims into the user's Rulebricks profile.
Today this supports:
- **Display name**
- **Avatar URL**
When configured, a user's profile is automatically populated from SSO identity data so they enter the application with a more native, pre-filled experience.
## Summary
The SSO Team tab is built to preserve the existing Rulebricks team model:
- One root administrator
- Child users beneath that admin
- Tenancy through tenant assignments
- Permissions through roles
SSO becomes a configuration layer on top of the same core authorization system the rest of the application already uses. For the recommended order in which to configure these sections, see the [recommended configuration pattern](/private-deployment/authentication#recommended-configuration-pattern).
## External Authentication
/private-deployment/authentication/token-passthrough
Delegate authentication to an upstream proxy when Rulebricks runs inside a private network and cannot host its own SSO flow.
# External Authentication
This page describes a **token passthrough** authentication mode for self-hosted Rulebricks deployments. Use this mode when Rulebricks lives inside an already-protected network — behind an identity-aware proxy, an internal API gateway, or a service mesh — and you want Rulebricks to trust the JWT that upstream system has already issued as the sole source of user identity, without running any OAuth / OIDC / password flow of its own.
This mode completely disables Rulebricks' own authentication surface: the login UI is hidden, password and magic-link flows are shut off, and identity is derived entirely from the header your proxy attaches to each request.
**Never consider this mode** on a public-facing deployment, on a cloud-hosted
tenant, or anywhere you cannot absolutely guarantee that every request
reaching Rulebricks has been through your gatekeeper. Rulebricks does **not**
verify the token signature in this mode – the entire security guarantee rests
on your upstream proxy.
## How It Works
```mermaid
sequenceDiagram
participant User
participant Proxy as Upstream Proxy
participant MW as Rulebricks Middleware
participant SB as Supabase
participant App as Rulebricks App
User->>Proxy: Request
Proxy->>MW: Request + X-Auth-Token header
alt First request or identity changed
MW->>MW: Decode JWT payload (no verify)
MW->>SB: Upsert auth.users (email match)
Note over SB: DB trigger creates public.users row
MW->>MW: Mint Supabase-compatible JWT
MW->>App: Forward with Supabase session cookie
else Same identity (fingerprint cache hit)
MW->>App: Forward unchanged
end
App->>User: Dashboard rendered as authenticated user
```
1. Every request enters our application's middleware.
2. If passthrough is enabled, the middleware extracts the token from a configurable header.
3. On first sight of a token (or a changed identity), the middleware:
- Decodes the JWT payload (base64, no signature verification).
- Upserts a user in Supabase `auth.users` matched by email.
- Mints a fresh Supabase-signed JWT (HMAC-SHA256 with your project's JWT secret) with `sub` equal to the Supabase user UUID.
- Sets the standard Supabase session cookie. All downstream Rulebricks code uses this cookie as if the user had logged in normally.
4. On subsequent requests for the same email identity, a SHA-256 fingerprint cache short-circuits the work: the middleware returns immediately and the existing session cookie is reused, even if the upstream proxy rotates the JWT string.
5. A user session lasts 24 hours by default. The upstream proxy must continue forwarding a token on every request; otherwise the session cookie alone is enough for the cookie's 24h lifetime, and a new identity or expired session will trigger re-upsert + re-mint.
## Enabling Passthrough via the Helm Chart
Token passthrough is enabled through a dedicated block in your `values.yaml`. When `global.externalAuth.enabled` is `true`, the chart automatically wires the required flags into the Rulebricks app's ConfigMap and Secret — no manual `env` overrides needed.
A minimal configuration looks like this:
```yaml
global:
email: 'admin@your-company.com'
supabase:
jwtSecret: ''
externalAuth:
enabled: true
# Optional overrides
header: 'X-Auth-Token'
claims:
email: 'email'
```
`global.supabase.jwtSecret` and `global.email` are sensitive — prefer sourcing
them via `global.secrets.secretRef` (External Secrets Operator,
sealed-secrets, or a manually created Kubernetes Secret) rather than pasting
them directly into `values.yaml`.
Once these values roll out, the app restarts into passthrough mode: the login UI disappears, the middleware starts reading the configured header on every request, and users provisioned by the upstream proxy flow through to the dashboard transparently.
## Required Chart Values
These three chart values **must** be set when passthrough is enabled. The app refuses to serve any request with a clear 500 if any of the env vars the chart derives from them is missing.
| Chart value | Purpose |
| :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `global.externalAuth.enabled` | Set to `true` to enable the mode. The chart emits both the server flag (`EXTERNAL_AUTH_TOKEN_PASSTHROUGH`) and the matching client flag (`NEXT_PUBLIC_EXTERNAL_AUTH_TOKEN_PASSTHROUGH`) so the browser bundle suppresses login UI and handles session cookies correctly. Any other value (including unset) disables it. |
| `global.supabase.jwtSecret` | Your Supabase project's JWT signing secret. Find it at Supabase Dashboard → Project Settings → API → JWT Settings → "JWT Secret". The middleware signs minted tokens with this, and Supabase validates them with the same secret on every request. Must match exactly. The chart value schema enforces this is non-empty when passthrough is on. |
| `global.email` | The email address of the administrator. The first user whose token carries this email becomes the workspace admin. All other users are provisioned as child members under that admin. Anchors the "one parent per self-hosted instance" invariant. |
`global.supabase.url` (managed mode) or the derived self-hosted Supabase URL, along with `global.supabase.anonKey` and `global.supabase.serviceKey`, also must be set; they are already required by Rulebricks in any self-hosted configuration, so nothing new there.
## Optional Chart Values
| Chart value | Default | Purpose |
| :--------------------------------- | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `global.externalAuth.header` | `Authorization` | Header name to read the external JWT from. If `Authorization` (the default), the `Bearer ` prefix is stripped automatically. Use something like `X-Auth-Token` for deployments where the `Authorization` header is already used for something else. |
| `global.externalAuth.claims.id` | `sub` | JWT claim path to read as the external stable user identifier. Stored in `user_metadata.external_sub` for reference. Not used for auth — Supabase UUIDs are the real identity. |
| `global.externalAuth.claims.email` | `email` | JWT claim path to read as the user's email. This is the only claim Rulebricks actually uses for user upsert, so it must exist and be unique per user. |
| `global.externalAuth.claims.name` | `name` | JWT claim path to read as the user's display name. Copied into `user_metadata.full_name`, which a DB trigger writes to `public.users.name`. |
| `global.externalAuth.publicPaths` | `[]` | YAML list of URL-path prefixes that should NOT require the token. The chart joins them with commas for the app. Useful for public forms (e.g. `['/form/', '/api/form/']`). Requests matching any prefix pass through middleware untouched. `/api/health`, `/api/metrics`, `/auth/denied`, `/api/self-hosted-branding`, and requests carrying `x-api-key` are always auto-exempt by the app. |
## Token Shape
Rulebricks accepts any valid 3-part JWT (`header.payload.signature`). It does not verify the signature — the upstream proxy is responsible for all verification. The algorithm field can be anything (HS256, RS256, ES256, `none`, etc.).
### Required Payload Claim
- `email` (configurable via `global.externalAuth.claims.email`) — The user's email address. Used as the identity key. If the JWT is missing this claim, the middleware returns 401.
### Recommended Payload Claims
- `sub` (configurable via `global.externalAuth.claims.id`) — A stable external identifier such as the IdP's internal user ID. Stored in `user_metadata.external_sub` and `user_metadata.external_claims` on the Supabase auth user for future cross-reference.
- `name` (configurable via `global.externalAuth.claims.name`) — A human-readable display name. Appears on the user's profile in Rulebricks.
### Optional Payload Claims
Any additional claims (e.g. `department`, `role`, `groups`, `cost_center`, `tenant`) are preserved on the auth user (in `user_metadata.external_claims`) and surfaced in the Rulebricks admin SSO-mapping UI. The admin can configure claim-based access / role / tenant / profile mappings from that UI — see [Claim Mapping](/private-deployment/authentication/claim-mapping).
### Example Token Payload
```json
{
"sub": "ext-user-f3a2",
"email": "alice@acme.com",
"name": "Alice Lim",
"department": "engineering",
"groups": ["developers", "rulebricks-users"],
"iat": 1712345000
}
```
### Example HTTP Request
```
GET /dashboard HTTP/1.1
Host: rulebricks.internal
X-Auth-Token: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJleHQtdXNlci1mM2EyIiwiZW1haWwiOiJhbGljZUBhY21lLmNvbSIsIm5hbWUiOiJBbGljZSBMaW0ifQ.
```
Signature bytes can be empty or anything else — they are discarded.
## User Lifecycle
Rulebricks' self-hosted workspace has **exactly one admin user** (the parent). Every other user is a child under that admin. Token passthrough preserves this invariant by anchoring the admin slot to `global.email`.
### Admin Signup
1. Operator sets `global.email: admin@acme.com` in the chart values.
2. The first time ANY passthrough user hits the app (possibly an ordinary user whose email is NOT the admin email), a placeholder admin row with `email = admin@acme.com` and `parent IS NULL` is created inside `sso-provision`.
3. When the actual admin user (someone whose token has `email: admin@acme.com`) signs in, the middleware finds the placeholder row by email, updates its metadata with the claims from their token, and the user takes over the admin slot transparently. No new row is created.
4. The admin then visits **Team → SSO** in the Rulebricks UI to configure claim-based access control and role mappings. Until this is done, all non-admin users default to the `developer` role.
### Regular User Signup
1. A user's browser request arrives at the proxy with no Rulebricks session.
2. The proxy forwards to Rulebricks with the user's JWT in the configured header.
3. Middleware sees no matching session cookie, a new token fingerprint, and upserts the user in `auth.users` keyed by email.
4. Supabase's DB trigger automatically creates matching rows in `public.users`, `public.usage`, and `public.keys`.
5. `/api/sso-provision` runs (invisibly, from the browser after sign-in completes): it finds the admin by `global.email`, sets `public.users.parent` for this user to the admin's UUID, and applies any configured role / tenant / profile mappings from `ssoRoleMapping`.
6. The user lands on `/dashboard`, fully provisioned.
### Sign-Out
There is no traditional sign-out in passthrough mode. If an end user triggers sign-out:
- The browser's Supabase session cookie is cleared.
- On the next request through the proxy, the middleware sees the token again and immediately restores the session (transparent re-login).
This is the expected behavior — in passthrough mode, the proxy owns the session lifetime, not Rulebricks.
## Claim Mapping
Once the admin has logged in, they should visit **Team → SSO** in the Rulebricks UI. This page automatically loads the claim schema from `user_metadata.external_claims` through an authenticated Rulebricks endpoint, avoiding large browser cookies for enterprise tokens with many groups, and lets the admin configure access, role, tenant, and profile mappings from the same interface used for traditional SSO.
See [Claim Mapping](/private-deployment/authentication/claim-mapping) for the full reference on each section.
Mappings are saved to `public.users.ssoRoleMapping` on the admin's row and re-applied on every subsequent user sign-in. Until mappings are configured, every non-admin user is provisioned with the default `developer` role.
The SSO tab is named "SSO" for historical reasons — the same UI works for
token passthrough because both use the same claim-based provisioning pipeline.
## Application Behavior Differences
When `global.externalAuth.enabled: true`:
- Routes like `/auth/signin`, `/auth/signup`, `/auth/forgotpass`, and `/auth/changepass` all redirect to `/dashboard`. There is no login UI.
- The top-nav **Sign in** and **Sign up** links are hidden.
- The dashboard-nav **Account** and **Sign out** links are hidden.
- Email / password signup, SSO sign-in, magic links, and password reset are disabled. Calling them does nothing meaningful.
- Supabase auto-token-refresh is disabled in the browser. Token rotation is handled by the middleware's identity fingerprint cache; non-identity claim changes apply when the session is re-minted.
- Public routes listed in `global.externalAuth.publicPaths` bypass the token check. Useful for customer-facing embedded forms. `/api/health`, `/api/metrics`, `/auth/denied`, `/api/self-hosted-branding`, and `x-api-key` API calls are auto-exempt without extra configuration.
- `/api/sso-provision` accepts an `identity_claims` body field (the decoded token) so it can apply access / role / tenant / profile mappings without calling an IdP userinfo endpoint.
- `/api/passthrough-claims` returns the current user's stored external claims to the Team → SSO UI. Claims are not stored in a browser cookie.
## RLS and Tenant Isolation
The Rulebricks database has row-level security policies on every user-facing table. These policies use `auth.uid()` — a Supabase function that returns the `sub` claim of the validated JWT.
In passthrough mode:
- The JWT Rulebricks mints always has `sub = `.
- `public.users.id = auth.users.id` (enforced by the DB trigger).
- So `auth.uid()` returns the correct Supabase UUID, and all existing RLS policies work unchanged.
The external token's `sub` is **never** used for authorization. It is stored as metadata for reference, but identity is always Rulebricks-generated Supabase UUIDs. This means:
- A user's tokens can carry different `sub` values over time (e.g., if the upstream system rotates identifiers) with no impact — emails are the matching key.
- Rulebricks UUIDs remain stable across token rotations.
- RLS policies — including the self-hosted "parent tenant" checks — behave identically to normal deployments.
## Local Testing
The simplest way to try passthrough is with a local Rulebricks chart install pointed at a local Supabase stack.
1. Install the Supabase CLI: `brew install supabase/tap/supabase`.
2. Start a local Supabase instance: `supabase start`. Note the printed `anon key`, `service_role key`, and `JWT secret`.
3. Create a `values-local.yaml` overlay for your chart install:
```yaml
global:
domain: 'localhost'
email: 'admin@test.local'
supabase:
url: 'http://127.0.0.1:54321'
anonKey: ''
serviceKey: ''
jwtSecret: ''
externalAuth:
enabled: true
header: 'X-Auth-Token'
supabase:
enabled: false
```
4. Install (or upgrade) the chart with this overlay:
```bash
helm upgrade --install rulebricks oci://ghcr.io/rulebricks/helm/stack \
--namespace rulebricks --create-namespace \
-f values-local.yaml
```
5. Craft a token. Any of these work:
- **[jwt.io](https://jwt.io)** — Paste a payload, copy the encoded token, ignore the signature.
- **Node one-liner**:
```bash
node -e '
const b64url = s => Buffer.from(s).toString("base64url");
const payload = { sub: "ext-001", email: "admin@test.local", name: "Test Admin" };
console.log(`${b64url(`{"alg":"none","typ":"JWT"}`)}.${b64url(JSON.stringify(payload))}.sig`);
'
```
6. Port-forward or ingress to the app service, then hit it:
```bash
TOKEN=
curl -i -H "X-Auth-Token: $TOKEN" http://localhost:3000/dashboard
```
First request: expect 200 HTML and Set-Cookie headers. Second request: expect a cache hit (no Set-Cookie).
7. For a full UI walkthrough, use a browser extension like ModHeader to inject the header on every request, then navigate to the Rulebricks UI.
Do not test passthrough against a shared production database. The passthrough
mode rewrites users and session cookies on every cold start; running it
against a multi-tenant production dataset can corrupt existing tenants. Use a
local Supabase or a dedicated staging project.
## Security Model
Passthrough mode is **explicitly trust-based**. Rulebricks does not verify the token signature against an IdP, does not validate the issuer, and does not check expiry or audience claims in any cryptographic sense. The entire security guarantee rests on:
1. The upstream proxy authenticating and verifying the token before forwarding.
2. The proxy **never** forwarding a token it did not issue itself or fully vet.
3. The network between the proxy and Rulebricks being a private network that nothing else can reach.
4. The operator setting `global.email` to an email only the real admin controls.
If any of those assumptions breaks, an attacker who can inject HTTP headers reaching Rulebricks can impersonate any user (including the admin) simply by crafting a JSON payload.
This is the intended trust model for the internal-network deployment scenario this feature is designed for. Deployments that cannot meet these assumptions should use the standard OIDC SSO mode instead — see the [SSO Overview](/private-deployment/authentication) for configuration.
## Troubleshooting
**Every request returns 500 "Token passthrough is enabled but required env vars are missing: …"**
Confirm that `global.externalAuth.enabled`, `global.supabase.jwtSecret`, and `global.email` are all set in your chart values, then redeploy. The preflight check also requires the standard Supabase environment the chart wires for any install (the Supabase URL and service key); the error message lists exactly which variables are missing. The chart's value schema also enforces `global.supabase.jwtSecret` is non-empty whenever passthrough is enabled, so a missing secret should fail at `helm install` time rather than at runtime.
**Every request returns 401 "Missing authentication token"**
The header configured at `global.externalAuth.header` is not present on the incoming request. Verify the upstream proxy is adding the header and that its name matches exactly (case-insensitive, but the `Bearer ` prefix stripping only applies when the value is left at the default `Authorization`).
**401 "Token missing required email claim"**
The token decodes but has no value at `global.externalAuth.claims.email` (default `email`). Adjust the claim path or update the proxy to include the email.
**401 "Invalid token format"**
The header value is not a 3-part dot-separated JWT. Check for trailing whitespace or truncation in the proxy.
**User reaches `/dashboard` but sees no data, and `supabase.auth.getUser()` returns a user**
Likely cause: `global.supabase.jwtSecret` does not match the Supabase project's JWT secret. Fix by copying the secret from Supabase Dashboard → Project Settings → API → JWT Settings into your chart values, then redeploy.
**Admin user sees "SSO provisioning is not yet active" banner**
Expected state until the admin configures role mappings. Go to **Team → SSO**, pick a role claim (e.g. `groups`), add mappings, and save.
**Non-admin users keep landing at `/auth/denied`**
The `accessMapping` rule in the admin's SSO configuration is rejecting their token. Rulebricks clears the local Supabase session before redirecting to `/auth/denied`; the upstream proxy must stop forwarding an allowed token, or the next proxied request can create a new session attempt. Verify the configured `claimPath` exists on the token and that its value is in the allowed list.
**Two "parent" users appear in the database**
Should never happen if the workflow above is followed. If it does, it means `global.email` was unset or changed between deploys. Fix: manually re-point the orphaned rows to a single parent via SQL, or contact support.
## Rulebricks CLI
/private-deployment/cli
Configure, deploy, and manage private Rulebricks instances with the Rulebricks CLI.
# Rulebricks CLI
The Rulebricks CLI is a management utility for configuring and deploying private Rulebricks instances onto Kubernetes clusters you already control. It focuses on three things: generating valid configuration values, sizing the application from the selected cluster's available resources, and deploying the Helm chart.
If you'd rather work with Helm directly, every value the CLI generates maps to the [Configuration Reference](/private-deployment/configuration-reference). The CLI simply automates the parts that are easy to get wrong, like Kafka topic sizing and cloud storage identity wiring.
## Installation
```bash
npm install -g @rulebricks/cli
```
## Prerequisites
Before running the CLI, you'll need:
- A valid **Rulebricks license key** (requested during configuration)
- An **available Kubernetes cluster** to deploy to
- **Node.js** >= 18
- **kubectl** configured for your cluster
- **Helm** >= 3.0
- A cloud CLI (`aws`, `gcloud`, or `az`) configured for your provider, if you want the wizard to discover clusters or refresh kubeconfig
Rulebricks requires TLS. You'll need either external-dns on your cluster for automatic DNS records, or access to manually add DNS records for the subdomains where your deployment will live.
### Cluster Setup
If you don't have a cluster yet, the [`cluster-setup/`](https://github.com/rulebricks/cli/tree/main/cluster-setup) directory in the CLI repository provides turnkey templates for AWS, Azure, and GCP. Each one creates the cluster (Kubernetes 1.34) plus the object storage and monitoring resources Rulebricks needs, wired to workload identity, and doubles as documentation for teams deploying to an existing cluster.
On AWS, a single CloudFormation stack creates the VPC, EKS cluster and node group, EBS CSI and Pod Identity add-ons, an S3 data bucket, and an Amazon Managed Prometheus workspace:
```bash
cd cluster-setup/aws
# Optional access check
AWS_REGION=us-east-1 bash check-aws-prereqs.sh
# Create the stack (named IAM roles require the capability flag)
aws cloudformation create-stack \
--stack-name rulebricks-cluster \
--region us-east-1 \
--template-body file://rulebricks-cluster.cfn.yaml \
--parameters file://parameters.json \
--capabilities CAPABILITY_NAMED_IAM
aws cloudformation wait stack-create-complete \
--stack-name rulebricks-cluster --region us-east-1
aws eks update-kubeconfig --name rulebricks-cluster --region us-east-1
```
Azure has an equivalent Bicep template (AKS with Workload Identity, a blob container, and Azure Monitor wiring), and GCP has gcloud guidance for GKE; see each directory's README. The templates are deployment-independent: they use EKS Pod Identity (or Azure federated identity credentials), and the CLI creates the namespace-scoped associations at `rulebricks deploy` time, so one cluster can host multiple deployments. For hand-managed clusters, the chart also accepts IRSA role ARNs directly; see [Storage & Backups](/private-deployment/storage-and-backups).
After the cluster exists and kubeconfig works, run `rulebricks init` and select it. The wizard can also refresh kubeconfig for EKS, GKE, or AKS when provider details are available.
## Quick Start
```bash
# Configuration wizard (generates values.yaml)
rulebricks init
# Deploy to your cluster
rulebricks deploy my-deployment
```
The generated Helm values pin one Rulebricks product version under `global.version`. That single semantic version selects the app, HPS, and HPS worker images together, so there's no per-image tag to keep in sync.
The wizard also handles cross-chart details that are easy to miss in hand-written values, including private registry image overrides, workload identity bindings, ClickStack vs. BYO tracing choices, app log shipping sinks, Kafka topic sizing, and external Redis/Kafka/PostgreSQL wiring.
## Commands
| Command | Description |
| :------------------------------------- | :--------------------------------------------------------------- |
| `rulebricks init` | Interactive setup wizard |
| `rulebricks configure [name]` | Update an existing deployment's configuration (deploy to apply) |
| `rulebricks deploy [name]` | Deploy to Kubernetes |
| `rulebricks upgrade [name]` | Upgrade to a new version |
| `rulebricks destroy ` | Remove a deployment (name required) |
| `rulebricks status [name]` | Show deployment health |
| `rulebricks logs [name] [component]` | Inspect services (`app`, `hps`, `workers`, `kafka`, `supabase`, `traefik`) |
| `rulebricks list` | List all deployments |
| `rulebricks clone ` | Clone a deployment configuration |
| `rulebricks open ` | Open the generated configuration files (name required) |
| `rulebricks benchmark [name]` | Run load tests against a deployment |
| `rulebricks backup [name]` | Run an on-demand database backup |
| `rulebricks restore [name]` | Restore the database from object storage |
Where `[name]` is omitted, the CLI prompts you to pick a deployment interactively. Use `rulebricks -h` to explore all commands, and add `-h` to any command for its options.
## Object Storage and Backups
The wizard collects a shared object storage backend (S3, Azure Blob, or GCS) for every deployment, and optionally enables scheduled database backups for self-hosted Supabase. `rulebricks backup` triggers an on-demand backup, and `rulebricks restore` lists backups in object storage and interactively restores one.
See [Storage & Backups](/private-deployment/storage-and-backups) for the full picture: the one-bucket model, per-provider identity, and backup configuration.
## Monitoring
Self-hosted deployments enable Prometheus monitoring and ClickStack observability by default. The wizard only asks whether you want to configure a Prometheus `remote_write` destination, or whether to disable ClickStack and send traces or app logs to a platform you already run. You can skip those steps and add them later.
The optional features step can also enable Valkey Admin + cache metrics. Valkey Admin is internal-only by default (open it with `kubectl port-forward`), while Redis/Valkey and Kafka-lag exporters feed the same Prometheus remote-write pipeline.
Application log shipping supports BYO Elasticsearch/Kibana and config-file targets for Loki or generic HTTP sinks. Distributed tracing remains one toggle with destination-specific configuration for Elastic APM, generic OTLP/HTTP (Tempo, Jaeger, Honeycomb, etc.), or Azure Monitor.
See [Monitoring](/private-deployment/monitoring), [Distributed Tracing](/private-deployment/tracing), and [Cache Observability](/private-deployment/cache-observability) for the full details.
The CLI offers a uniquely wide variety of customization options (multi-cloud,
hybrid vs. self-hosted database deployment, custom email templates, and more),
and not every combination has been validated. If you hit an issue, email
[support@rulebricks.com](mailto:support@rulebricks.com) or open an issue on
[GitHub](https://github.com/rulebricks/cli/issues).
## YAML Reference
/private-deployment/configuration-reference
Reference for the configuration values in the Rulebricks Helm chart.
# YAML Reference
This page is a reference for the configuration values in the Rulebricks Helm chart, organized by component. Conceptual guides live elsewhere; each section links to the relevant one. For the exhaustive list of every value (including subchart internals), see the chart's [values.yaml](https://github.com/rulebricks/helm/blob/main/values.yaml) and [values.schema.json](https://github.com/rulebricks/helm/blob/main/values.schema.json).
### Core Configuration
| Parameter | Type | Default | Required | Description |
| :-------------------------- | :------ | :------------------------- | :------- | :------------------------------------------------------------------------------------------------------ |
| `global.domain` | string | `""` | **Yes** | The domain name for your Rulebricks instance. Must be a domain you control. |
| `global.email` | string | `"support@rulebricks.com"` | **Yes** | Email address for Let's Encrypt certificate registration. |
| `global.licenseKey` | string | `"evaluation"` | **Yes** | Your Rulebricks Enterprise license key. |
| `global.version` | string | `""` | **Yes** | Rulebricks product version. One semver selects the app, HPS, HPS worker, and migration images together. |
| `global.tlsEnabled` | boolean | `false` | No | Enable HTTPS. Set to `true` after DNS is configured or when using external-dns. |
| `global.externalDnsEnabled` | boolean | `false` | No | Enable external-dns annotations on ingresses for automatic DNS management. |
#### Version Pinning
Production deployments should pin two versions: a Helm chart version (via `helm install --version`) and a Rulebricks product version (via `global.version`). The chart version controls Kubernetes packaging and infrastructure templates; `global.version` selects the application images. See [Installing Updates](/private-deployment/quick-start#installing-updates).
```yaml
global:
version: '1.8.17'
```
#### Image Registry, Digests, and Priority Classes
For private registry or air-gapped installs, the chart can repoint images to your registry mirror and pin image digests:
| Parameter | Type | Default | Description |
| :------------------------------- | :------ | :------ | :---------------------------------------------------------------------------------------------------- |
| `global.imageRegistry` | string | `""` | Repoints Rulebricks image references to your registry host while keeping the `rulebricks/*` path |
| `global.imageDigests` | object | `{}` | Optional image digest map generated from the chart image manifest |
| `global.imagePullSecrets` | list | `[]` | Pull secrets for private image registries |
| `global.images.*` | object | pinned | Shared leaf images (init/hook/test utilities: `postgres17`, `node24`, `rclone`, and so on) used by migrations, backups, and bootstrap jobs |
| `global.priorityClasses.enabled` | boolean | `true` | Creates release-scoped `critical` and `burst` scheduling priorities for infrastructure and worker pods |
The single `global.imageRegistry` value covers images rendered through the Rulebricks chart helpers. Some upstream subcharts use their own image keys; the Rulebricks CLI rewrites those keys for you when you configure a registry mirror. Hand-installs should check the image comments in the chart's `values.yaml` for the matching Traefik, KEDA, cert-manager, Vector, Strimzi, and external-dns overrides.
#### Domain Configuration
Your domain should follow the pattern: `rulebricks.yourdomain.com`
When self-hosting Supabase, an additional subdomain is created: `supabase.rulebricks.yourdomain.com`
```yaml
global:
domain: 'rulebricks.acme.com'
email: 'devops@acme.com'
```
**Important:** The domain must resolve to your cluster's load balancer before
enabling TLS. See [DNS Configuration](#dns-external-dns) for automatic setup.
### SMTP Configuration
SMTP is **required** for user authentication flows (invitations, password resets, email verification).
| Parameter | Type | Default | Description |
| :--------------------- | :------ | :-------------------------- | :------------------------------------------------- |
| `global.smtp.host` | string | `"smtp.mailtrap.io"` | SMTP server hostname |
| `global.smtp.port` | integer | `2525` | SMTP server port (typically 25, 465, 587, or 2525) |
| `global.smtp.user` | string | `""` | SMTP authentication username |
| `global.smtp.pass` | string | `""` | SMTP authentication password |
| `global.smtp.from` | string | `"no-reply@rulebricks.com"` | Sender email address |
| `global.smtp.fromName` | string | `"Rulebricks"` | Sender display name |
#### Production SMTP Providers
| Provider | Host | Port | Notes |
| :------- | :---------------------------------- | :--- | :--------------------------- |
| AWS SES | `email-smtp..amazonaws.com` | 587 | Requires verified domain |
| SendGrid | `smtp.sendgrid.net` | 587 | API key as password |
| Mailgun | `smtp.mailgun.org` | 587 | Domain verification required |
| Postmark | `smtp.postmarkapp.com` | 587 | Server token as password |
```yaml
global:
smtp:
host: 'email-smtp.us-east-1.amazonaws.com'
port: 587
user: ''
pass: ''
from: 'no-reply@yourdomain.com'
fromName: 'Your Company - Rulebricks'
```
### Supabase Keys
These JWT keys are used for authentication between components.
| Parameter | Type | Description |
| :--------------------------- | :----- | :------------------------------------------ |
| `global.supabase.anonKey` | string | Public/anonymous key for client-side auth |
| `global.supabase.serviceKey` | string | Service role key for server-side operations |
| `global.supabase.jwtSecret` | string | JWT signing secret (self-hosted only) |
**Security:** For production, generate new keys using Supabase's key generator
or a secure random string generator. The chart no longer ships working demo
Supabase keys.
#### Managed Supabase (Cloud)
When using Supabase Cloud instead of self-hosting:
| Parameter | Type | Description |
| :---------------------------- | :----- | :------------------------------------------------- |
| `global.supabase.url` | string | Project URL (e.g., `https://abcd1234.supabase.co`) |
| `global.supabase.projectRef` | string | Project reference ID (derived from URL if empty) |
| `global.supabase.accessToken` | string | Personal access token for Supabase CLI |
```yaml
global:
supabase:
url: 'https://abcd1234.supabase.co'
anonKey: ''
serviceKey: ''
accessToken: ''
supabase:
enabled: false # Disable self-hosted Supabase
```
### Email Templates
Customize authentication email subjects and templates:
```yaml
global:
supabase:
emails:
subjects:
invite: 'Join your team on Rulebricks'
confirmation: 'Confirm Your Email'
recovery: 'Reset Your Password'
emailChange: 'Confirm Email Change'
templates:
invite: 'https://your-cdn.com/templates/invite.html'
confirmation: 'https://your-cdn.com/templates/verify.html'
recovery: 'https://your-cdn.com/templates/password_change.html'
emailChange: 'https://your-cdn.com/templates/email_change.html'
```
### AI Features
Enable AI-powered rule generation (optional):
| Parameter | Type | Default | Description |
| :----------------------- | :------ | :------ | :----------------- |
| `global.ai.enabled` | boolean | `false` | Enable AI features |
| `global.ai.openaiApiKey` | string | `""` | OpenAI API key |
```yaml
global:
ai:
enabled: true
openaiApiKey: 'sk-...'
```
### SSO Configuration
Enable Single Sign-On (SSO) via OpenID Connect (OIDC). For how user provisioning, claim mapping, and token passthrough work, see [Authentication & SSO](/private-deployment/authentication).
| Parameter | Type | Default | Description |
| :------------------------ | :------ | :------ | :-------------------------------------------------------- |
| `global.sso.enabled` | boolean | `false` | Enable SSO |
| `global.sso.provider` | string | `""` | Provider name (azure, google, okta, keycloak, ory, other) |
| `global.sso.url` | string | `""` | Provider URL (required for all except Google) |
| `global.sso.clientId` | string | `""` | OAuth client ID |
| `global.sso.clientSecret` | string | `""` | OAuth client secret |
#### Provider Types
You will need to go to your Supabase dashboard, and configure your provider in the Authentication tab. If you don't see your provider, use _Keycloak_; our proxy uses it as a bridge.
- **Native Providers** (`azure`, `google`, `okta`, `keycloak`): Supabase handles authentication directly.
- **Custom Providers** (`ory`, `other`): The app uses a built-in OIDC proxy we manage to translate paths for your IdP.
#### Identity Provider Setup
You must configure your Identity Provider (IdP) with the following settings:
- **Scopes**: `openid`, `email`, `profile`
- **Response Type**: `code`
- **Grant Types**: `authorization_code`, `refresh_token`
- **Auth Method**: `http body`
- **Redirect URIs**:
1. `https:///api/sso-proxy/callback`
2. `/auth/v1/callback`
### External Secrets
For enterprise deployments using external secret management:
| Parameter | Type | Description |
| :------------------------------- | :----- | :--------------------------------- |
| `global.secrets.secretRef` | string | Name of existing Kubernetes secret |
| `global.secrets.secretRefKeys.*` | object | Key name mappings |
```yaml
global:
secrets:
secretRef: 'rulebricks-external-secrets'
secretRefKeys:
licenseKey: 'RULEBRICKS_LICENSE'
smtpUser: 'SMTP_USERNAME'
smtpPass: 'SMTP_PASSWORD'
supabaseServiceKey: 'SUPABASE_SERVICE'
supabaseAccessToken: 'SUPABASE_TOKEN'
openaiApiKey: 'OPENAI_KEY'
ssoClientId: 'SSO_CLIENT_ID'
ssoClientSecret: 'SSO_CLIENT_SECRET'
```
The Supabase anon key is public by design and is configured via `global.supabase.anonKey` rather than through the secret reference.
### External Authentication (Token Passthrough)
For deployments behind an identity-aware proxy that has already authenticated the user, `global.externalAuth.*` (`enabled`, `header`, `claims.id/email/name`, `publicPaths`) delegates authentication to the upstream proxy. This carries real security implications; read [Token Passthrough](/private-deployment/authentication/token-passthrough) before enabling.
### Scheduling, Labels, and Annotations
| Parameter | Type | Default | Description |
| :------------------------------------------- | :----- | :------ | :------------------------------------------------------------- |
| `global.scheduling.nodeSelector` | object | `{}` | Node selector for workloads that inherit global scheduling |
| `global.scheduling.tolerations` | list | `[]` | Tolerations (e.g. for ARM64-tainted node pools) |
| `global.scheduling.affinity` | object | `{}` | Affinity rules; prefer soft rules so installs aren't blocked |
| `global.labels` / `global.annotations` | object | `{}` | Applied to all resource metadata (compliance, cost allocation) |
| `global.podLabels` / `global.podAnnotations` | object | `{}` | Applied to pod templates only (e.g. mesh injection) |
Rulebricks workloads and migration jobs honor `global.scheduling`; dependency subcharts (Kafka, Supabase, cert-manager, Traefik, KEDA, Vector) may require their own scheduling values. Most components also accept per-component `podLabels` and `podDisruptionBudget` blocks for clusters with admission policies.
### Object Storage & Backups
| Value prefix | Purpose |
| :----------------- | :-------------------------------------------------------------------- |
| `global.storage.*` | Shared bucket, region, per-provider identity, and key prefixes |
| `backup.*` | Scheduled database backups for self-hosted Supabase (Barman, CronJob) |
Full tables, identity options per provider, and the backup/restore workflow live in [Storage & Backups](/private-deployment/storage-and-backups).
## Rulebricks Application
The core application and high-performance solver (HPS).
### Application Image
| Parameter | Type | Default | Description |
| :-------------------------------- | :----- | :---------------- | :---------------------- |
| `rulebricks.app.image.registry` | string | `"docker.io"` | Docker image registry |
| `rulebricks.app.image.repository` | string | `"rulebricks/app"` | Docker image repository |
| `rulebricks.app.image.pullPolicy` | string | `"IfNotPresent"` | Image pull policy |
Image tags are selected by `global.version`, the single product semver shared by the app, HPS, and HPS worker images. There is no per-component tag to set.
### Logging Configuration
| Parameter | Type | Default | Description |
| :---------------------------------------- | :------ | :------------------ | :------------------------------------------------------------------------------------ |
| `rulebricks.app.logging.enabled` | boolean | `true` | Enable decision logging |
| `rulebricks.app.logging.kafkaBrokers` | string | `""` | Kafka brokers (auto-discovered if empty) |
| `rulebricks.app.logging.kafkaTopic` | string | `"logs"` | Kafka topic for logs |
| `rulebricks.app.logging.kafkaTopicPrefix` | string | `"com.rulebricks."` | Prefix applied to all Kafka topic names; set `""` to disable prefixing |
| `rulebricks.app.logging.kafkaSsl` | boolean | `false` | Enable TLS for external Kafka brokers |
| `rulebricks.app.logging.kafkaSasl.*` | object | `{}` | SASL auth for external Kafka: `aws-iam`, `oauthbearer`, `scram-sha-256`, `scram-sha-512`, or `plain` |
For how decision logs flow through the system, see [Decision Logs](/private-deployment/decision-logs). For external Kafka authentication (including AWS MSK IAM and Azure Event Hubs examples), see [External Kafka & Redis](/private-deployment/external-services#authentication-sslsasl).
### Ingress
| Parameter | Type | Default | Description |
| :-------------------------------- | :------ | :---------- | :------------------------------------------------------------------- |
| `rulebricks.ingress.enabled` | boolean | `true` | Enable chart-managed routing |
| `rulebricks.ingress.app.enabled` | boolean | `true` | Set `false` when an external auth proxy owns the app catch-all route |
| `rulebricks.ingress.type` | string | `"ingress"` | `ingress` (standard) or `gateway-api` (renders `HTTPRoute`) |
| `rulebricks.ingress.className` | string | `"traefik"` | Ingress class name |
| `rulebricks.ingress.hostname` | string | `""` | Override when the app hostname differs from `global.domain` |
| `rulebricks.ingress.gatewayApi.*` | object | `{}` | `gatewayName` / `gatewayNamespace` for Gateway API mode |
| `rulebricks.ingress.paths` | list | `[{...}]` | List of paths to route |
Gateway API mode requires an implementation (Envoy Gateway, Cilium, Istio) in your cluster.
### Redis
A Redis-compatible cache is used for caching and session management. The bundled deployment runs [Valkey](https://valkey.io/) (the OSI-licensed Redis fork); the `redis` values key is retained for compatibility with existing overrides.
| Parameter | Type | Default | Recommendation |
| :------------------------------------------- | :------ | :-------- | :--------------------------- |
| `rulebricks.redis.resources.requests.cpu` | string | `"200m"` | Increase for high traffic |
| `rulebricks.redis.resources.requests.memory` | string | `"256Mi"` | |
| `rulebricks.redis.resources.limits.cpu` | string | `"500m"` | |
| `rulebricks.redis.resources.limits.memory` | string | `"4Gi"` | Increase for large rule sets |
| `rulebricks.redis.persistence.enabled` | boolean | `true` | Keep enabled for production |
| `rulebricks.redis.persistence.size` | string | `"4Gi"` | |
| `rulebricks.redis.persistence.storageClass` | string | `"gp3"` | Match your storage class |
To use a managed Redis instance (ElastiCache, Memorystore, Upstash, and so on) instead of the bundled deployment, set `rulebricks.redis.enabled: false` and configure the external block:
| Parameter | Type | Default | Description |
| :-------------------------------------------- | :------ | :------ | :--------------------------------------------------------- |
| `rulebricks.redis.external.host` | string | `""` | Redis hostname (required when `redis.enabled` is `false`) |
| `rulebricks.redis.external.port` | integer | `6379` | Redis port |
| `rulebricks.redis.external.password` | string | `""` | Inline password (injected at runtime, never in ConfigMaps) |
| `rulebricks.redis.external.existingSecret` | string | `""` | Existing secret name (preferred for production) |
| `rulebricks.redis.external.existingSecretKey` | string | `"redis-password"` | Key within the secret |
| `rulebricks.redis.external.tls.enabled` | boolean | `false` | Use `rediss://` |
| `rulebricks.redis.external.httpApi.*` | object | `{}` | Upstash-style HTTP API (url/token or existing secret) |
See [External Kafka & Redis](/private-deployment/external-services#externalizing-redis) for how each component connects.
#### Cache Observability
| Parameter | Type | Default | Description |
| :---------------------------------------- | :------ | :------ | :------------------------------------------------------------------------------ |
| `rulebricks.cache.redisExporter.enabled` | boolean | `true` | Deploys a Prometheus exporter for the cache (works for bundled and external) |
| `rulebricks.cache.valkeyAdmin.enabled` | boolean | `false` | Optional Valkey Admin web console; internal-only unless exposed through ingress |
### High Performance Server (HPS)
HPS handles rule execution with horizontal scaling. For the sizing model behind these values (partition ceilings, worker resources, KEDA thresholds), see [Performance & Scaling](/private-deployment/performance).
| Parameter | Type | Default | Description |
| :------------------------------------------ | :------ | :---------------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
| `rulebricks.hps.enabled` | boolean | `true` | Enable HPS |
| `rulebricks.hps.replicas` | integer | `3` | Number of HPS gateway replicas |
| `rulebricks.hps.workers.enabled` | boolean | `true` | Enable worker pods |
| `rulebricks.hps.workers.replicas` | integer | `8` | Base worker replica count |
| `rulebricks.hps.workers.solutionPartitions` | integer | `128` | Partition count of the `solution` topic; the worker fleet's concurrency ceiling |
| `rulebricks.hps.workers.resources.*` | object | 250m request / 1 CPU limit, 1Gi memory | Per-worker resources; scale out for throughput |
| `rulebricks.hps.workers.keda.*` | object | see [Performance & Scaling](/private-deployment/performance#keda-autoscaling) | KEDA autoscaling configuration |
| `rulebricks.hps.imagePrepull.enabled` | boolean | `true` | Pre-pulls HPS server and worker images on nodes before burst scale-out |
#### HPS Gateway Autoscaling
The HPS gateway (the API pods, distinct from the workers) is also KEDA-scaled by default, on `solution-response` topic lag and CPU:
| Parameter | Type | Default | Description |
| :---------------------------------------- | :------ | :------ | :--------------------------------------- |
| `rulebricks.hps.keda.enabled` | boolean | `true` | Enable gateway autoscaling |
| `rulebricks.hps.keda.minReplicaCount` | integer | `3` | Baseline gateway fleet |
| `rulebricks.hps.keda.maxReplicaCount` | integer | `8` | Gateway scale-out ceiling |
| `rulebricks.hps.keda.pollingInterval` | integer | `15` | Metric poll interval (seconds) |
| `rulebricks.hps.keda.cooldownPeriod` | integer | `600` | Wait before scaling down (seconds) |
| `rulebricks.hps.keda.lagThreshold` | integer | `300` | `solution-response` lag per replica |
| `rulebricks.hps.keda.cpuThreshold` | integer | `60` | CPU utilization target (%) |
#### HPS Image
| Parameter | Type | Default | Description |
| :-------------------------------- | :----- | :----------------- | :---------------------- |
| `rulebricks.hps.image.registry` | string | `"docker.io"` | Docker image registry |
| `rulebricks.hps.image.repository` | string | `"rulebricks/hps"` | Docker image repository |
| `rulebricks.hps.image.pullPolicy` | string | `"Always"` | Image pull policy |
HPS and worker image tags are derived from `global.version` (`` and `worker-` respectively).
#### HPS Resources
| Parameter | Type | Default |
| :----------------------------------------- | :----- | :-------- |
| `rulebricks.hps.resources.requests.cpu` | string | `"1000m"` |
| `rulebricks.hps.resources.requests.memory` | string | `"1Gi"` |
| `rulebricks.hps.resources.limits.cpu` | string | `"2000m"` |
| `rulebricks.hps.resources.limits.memory` | string | `"2Gi"` |
## Database (Supabase)
### Self-Hosted vs. Managed
| Mode | `supabase.enabled` | Use Case |
| :-------------- | :----------------- | :------------------------------------- |
| Self-hosted | `true` | Full control, air-gapped environments |
| Managed (Cloud) | `false` | Simplified operations, managed backups |
### Self-Hosted Configuration
| Parameter | Type | Default | Description |
| :----------------------------------- | :------ | :------------------------------- | :-------------------------- |
| `supabase.enabled` | boolean | `true` | Deploy self-hosted Supabase |
| `supabase.secret.db.username` | string | `"postgres"` | Database username |
| `supabase.secret.db.password` | string | `""` | Database password |
| `supabase.secret.db.database` | string | `"postgres"` | Database name |
| `supabase.secret.dashboard.username` | string | `"supabase"` | Studio dashboard username |
| `supabase.secret.dashboard.password` | string | `""` | Studio dashboard password |
#### Database Resources
| Parameter | Type | Default | Production Recommendation |
| :----------------------------------------- | :------ | :------- | :--------------------------- |
| `supabase.db.resources.requests.cpu` | string | `"500m"` | `"1000m"` or higher |
| `supabase.db.resources.requests.memory` | string | `"1Gi"` | `"2Gi"` or higher |
| `supabase.db.persistence.enabled` | boolean | `true` | Always `true` for production |
| `supabase.db.persistence.size` | string | `"10Gi"` | Based on data volume |
| `supabase.db.persistence.storageClassName` | string | `"gp3"` | Use fast storage |
#### External PostgreSQL
The Supabase services can run in-cluster while pointing at an external PostgreSQL server (e.g. a managed instance). PostgreSQL 17 (recommended) and 16 are supported; see [Supported PostgreSQL Versions](/private-deployment/external-services#supported-postgresql-versions). The primary switch is `supabase.externalDatabase.enabled: true`, which stops the bundled database from deploying and points every Supabase service at your server; also set `supabase.db.enabled: false` so the chart renders the in-cluster `db` alias (an `ExternalName` service some components expect). Configure the connection once:
| Parameter | Type | Default | Description |
| :----------------------------------------- | :------ | :------------------------------- | :-------------------------------------------------- |
| `supabase.externalDatabase.enabled` | boolean | `false` | Use an external PostgreSQL server |
| `supabase.externalDatabase.host` | string | `""` | PostgreSQL host shared across Supabase services |
| `supabase.externalDatabase.port` | integer | `5432` | PostgreSQL port |
| `supabase.externalDatabase.secretRef` | string | `""` | Existing secret with credentials (else `secret.db`) |
| `supabase.externalDatabase.secretRefKey.*` | object | `username`/`password`/`database` | Key mappings within the secret; set `host`/`port` keys to source those from the same secret too |
| `supabase.externalDatabase.bootstrap.*` | object | enabled | Optional one-time initialization of a bare database |
Migration jobs reach the same server through `migrations.externalDb.*`.
### Kong Ingress
| Parameter | Type | Default | Description |
| :-------------------------------- | :------ | :---------- | :-------------------------- |
| `supabase.kong.ingress.enabled` | boolean | `true` | Enable Supabase API ingress |
| `supabase.kong.ingress.className` | string | `"traefik"` | Must match Traefik class |
### Studio and Auth URLs
Supabase Studio can be exposed on its own hostname via `supabase.studio.ingress.*` (`enabled`, `type`, `className`, `hostname`, defaulting to `studio.`); dashboard credentials come from `supabase.secret.dashboard.*`. When your application hostname differs from the base domain, override the computed auth URLs with `supabase.auth.siteUrl`, `supabase.auth.externalUrl`, and `supabase.auth.additionalRedirectUrls`.
## Migrations
| Parameter | Type | Default | Description |
| :----------------------------------- | :------ | :-------- | :-------------------------------------------------------------------------------------------------------------------------- |
| `migrations.strategy` | string | `"hook"` | `hook` (post-install/upgrade), `deployment` (regular resource, supports post-rendering), or `manual` (render without hooks) |
| `migrations.ttlSecondsAfterFinished` | integer | `2592000` | Seconds to retain completed migration Jobs and pod logs |
| `migrations.backoffLimit` | integer | `3` | Retries before marking the Job failed |
| `migrations.externalDb.*` | object | `{}` | Host/port/secret for migrations against an external PostgreSQL |
Migration pods inherit `global.scheduling` and accept their own `nodeSelector`, `tolerations`, and `annotations`.
## Message Queue (Kafka)
Kafka handles async rule execution and logging.
### Basic Settings
| Parameter | Type | Default | Description |
| :------------------------- | :------ | :--------- | :------------------------------------------------------------ |
| `kafka.enabled` | boolean | `true` | Deploy the in-cluster Strimzi Kafka broker and Topic Operator |
| `kafka.version` | string | `"4.2.0"` | Kafka version supported by the bundled Strimzi operator |
| `kafka.replicas` | integer | `1` | Broker replica count |
| `kafka.storage.size` | string | `"20Gi"` | Broker persistent storage size |
| `kafka.storage.class` | string | `"gp3"` | Broker storage class |
| `kafka.priorityClassName` | string | `""` | PriorityClass for the broker pod, usually filled by the CLI |
| `strimzi-kafka-operator.*` | object | see values | Operator images and namespace watch settings |
The bundled broker is managed by Strimzi and runs in KRaft mode. The chart does not expose the old Bitnami `kafka.kraft.*`, `kafka.zookeeper.*`, or `kafka.controller.*` values.
A Kafka Prometheus exporter is also deployed by default (`rulebricks.kafkaExporter.enabled: true`) for consumer-group lag metrics; it inherits `rulebricks.app.logging.kafkaSsl` / `kafkaSasl` for external brokers, so no exporter-specific auth values are needed.
### Kafka Topics
The chart manages application topics explicitly rather than relying on broker auto-creation. For the in-cluster broker, `kafka.topics` renders Strimzi `KafkaTopic` resources that the Topic Operator reconciles. When you externalize Kafka with a token-auth bridge, the same topic list can feed the external topic provisioning hook.
| Parameter | Type | Default | Description |
| :------------------------------------- | :------ | :----------- | :-------------------------------------------------------------------------- |
| `kafka.topics` | list | three topics | Per-topic name, partitions, replication, and config |
| `kafka.provisioning.enabled` | boolean | `true` | Enable the external-Kafka topic creation hook when `kafka.enabled: false` |
| `kafka.provisioning.replicationFactor` | integer | `3` | Replication factor used by that external provisioning hook |
The defaults define `solution`, `solution-response`, and `logs` topics with per-topic retention and size limits. Each entry looks like:
```yaml
kafka:
topics:
- name: com.rulebricks.solution
partitions: 128
replicas: 1
config:
retention.ms: '300000'
max.message.bytes: '2097152'
# ... see the chart's values.yaml for the full default topic list
```
Topic names must carry the configured `kafkaTopicPrefix`. If you change the
prefix, change these names to match. The CLI does this automatically. The
`solution` topic's partition count must also equal
`rulebricks.hps.workers.solutionPartitions`; see [Performance &
Scaling](/private-deployment/performance).
When using external Kafka (`kafka.enabled: false`) without the token-auth bridge, topics are customer-managed. See [External Kafka & Redis](/private-deployment/external-services#topics-to-pre-create).
### Kafka Bridge (Token-Auth External Kafka)
When external Kafka uses a token-based SASL mechanism (AWS MSK IAM, GCP OAUTHBEARER), Vector consumes through a kafka-proxy bridge sidecar that authenticates upstream via workload identity:
| Parameter | Type | Default | Description |
| :----------------------------------- | :------ | :------ | :-------------------------------------------- |
| `kafkaBridge.enabled` | boolean | `false` | Enable the bridge sidecar path |
| `kafkaBridge.provider` | string | `""` | `aws` or `gcp` |
| `kafkaBridge.region` | string | `""` | Region (required for AWS MSK IAM signing) |
| `kafkaBridge.brokers` | string | `""` | Upstream managed-Kafka broker list |
| `kafkaBridge.localPort` | integer | `19092` | Local plaintext port Vector consumes from |
| `kafkaBridge.image` | string | `"docker.io/rulebricks/kafka-proxy:0.4.3"` | kafka-proxy sidecar image (override for registry mirrors) |
| `kafkaBridge.awsRoleArn` | string | `""` | IRSA role ARN (AWS) |
| `kafkaBridge.gcpServiceAccountEmail` | string | `""` | Workload Identity service account email (GCP) |
The CLI sets all of this automatically when you externalize Kafka with AWS MSK IAM or GCP OAUTHBEARER. See [External Kafka & Redis](/private-deployment/external-services#authentication-sslsasl).
### High Availability
```yaml
kafka:
replicas: 3
```
Internal replication factors (`default.replication.factor`, offsets, transaction log) are derived from `kafka.replicas` automatically. The chart pins `min.insync.replicas: 1` on the broker so a replica loss never blocks the latency-sensitive RPC topics; entries for these keys under `kafka.config` are overridden by the chart-managed values. Per-topic replication for long-lived data (e.g. `logs`) is set on `kafka.topics`.
## Ingress (Traefik)
Traefik handles all incoming traffic and TLS termination.
| Parameter | Type | Default | Description |
| :-------------------------------- | :------ | :---------- | :----------------- |
| `traefik.enabled` | boolean | `true` | Deploy Traefik |
| `traefik.ingressClass.name` | string | `"traefik"` | Ingress class name |
| `traefik.autoscaling.enabled` | boolean | `true` | Enable HPA |
| `traefik.autoscaling.minReplicas` | integer | `1` | Minimum replicas |
| `traefik.autoscaling.maxReplicas` | integer | `4` | Maximum replicas |
Traefik resources, ports, and persistence follow the upstream subchart's values under `traefik.*`; the chart's defaults expose 80/443 and need no changes for typical deployments.
## Autoscaling (KEDA)
KEDA provides event-driven autoscaling for HPS workers. Scaling behavior is configured per-worker under `rulebricks.hps.workers.keda.*`; see [Performance & Scaling](/private-deployment/performance).
| Parameter | Type | Default | Description |
| :------------------ | :------ | :------ | :--------------------------- |
| `keda.enabled` | boolean | `true` | Deploy KEDA |
| `keda.crds.install` | boolean | `false` | CRDs managed by parent chart |
## Certificates (cert-manager)
cert-manager provisions TLS certificates from Let's Encrypt.
| Parameter | Type | Default | Description |
| :-------------------------- | :------ | :------ | :--------------------------- |
| `cert-manager.enabled` | boolean | `true` | Deploy cert-manager |
| `cert-manager.crds.enabled` | boolean | `false` | CRDs managed by parent chart |
The chart creates a `ClusterIssuer` for Let's Encrypt production and `Certificate` resources for your domain(s).
## Decision Logging (Vector & ClickHouse)
| Value prefix | Purpose |
| :------------- | :------------------------------------------------------------------------------------------------------- |
| `vector.*` | Decision-log aggregator consuming the Kafka `logs` topic; sinks and replicas |
| `clickhouse.*` | Query engine over the decision log archive, with local persistence for ClickStack telemetry when enabled |
Vector's Kafka connection (brokers, TLS, SASL, and the prefixed log topic) is templated automatically into a `vector-kafka-env` ConfigMap from `rulebricks.app.logging.*`; you don't set it by hand. How the pipeline fits together, the values tables, and custom sink examples live in [Decision Logs](/private-deployment/decision-logs).
## Built-in Observability (ClickStack, Tracing, and App Logs)
| Value prefix | Purpose |
| :---------------------------- | :--------------------------------------------------------------------------------------- |
| `global.clickstack.enabled` | Cross-chart switch for the built-in ClickStack observability backend, enabled by default |
| `clickstack.hyperdx.*` | HyperDX UI, ingress, and resources |
| `clickstack.collector.*` | OpenTelemetry gateway and node-agent resources |
| `clickstack.ferretdb.*` | Metadata store used by HyperDX |
| `global.tracing.*` | Optional BYO tracing collector when ClickStack is disabled |
| `vector-agent.*` | Optional Kubernetes app/container log shipping to a customer-managed log backend |
ClickStack provides the default in-cluster traces, operational logs, and observability UI. If your platform team already has a tracing or log backend, disable ClickStack and configure `global.tracing.*` or `vector-agent.*` instead. See [Monitoring](/private-deployment/monitoring) for the operator story and examples.
## DNS (external-dns)
external-dns automatically creates DNS records for your ingresses.
| Parameter | Type | Default | Description |
| :--------------------------- | :------ | :-------------- | :--------------------------- |
| `external-dns.enabled` | boolean | `false` | Deploy external-dns |
| `external-dns.provider.name` | string | `"route53"` | DNS provider |
| `external-dns.sources` | list | `["ingress"]` | Resource types to watch |
| `external-dns.domainFilters` | list | `[]` | Restrict to specific domains |
| `external-dns.policy` | string | `"upsert-only"` | Record management policy |
### Provider Configuration
#### AWS Route53
```yaml
external-dns:
enabled: true
provider:
name: route53
# Uses IRSA - ensure service account has Route53 permissions
```
#### Cloudflare
```yaml
external-dns:
enabled: true
provider:
name: cloudflare
extraEnvVars:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-api-token
key: api-token
```
#### Google Cloud DNS
```yaml
external-dns:
enabled: true
provider:
name: google
google:
project: 'your-gcp-project'
```
#### Azure DNS
```yaml
external-dns:
enabled: true
provider:
name: azure
azure:
resourceGroup: 'your-resource-group'
subscriptionId: 'your-subscription-id'
```
## Monitoring (Prometheus)
| Parameter | Type | Default | Description |
| :--------------------------- | :------ | :--------- | :-------------------------------------- |
| `monitoring.enabled` | boolean | `true` | Enable monitoring |
| `rulebricks.metrics.enabled` | boolean | `true` | Rulebricks ServiceMonitors |
| `kube-prometheus-stack.*` | object | see values | Upstream Prometheus stack configuration |
What's scraped, the metric label policy, remote write, and retention examples live in [Monitoring](/private-deployment/monitoring).
## Storage
### StorageClass
The chart can create a gp3 StorageClass for AWS EBS:
| Parameter | Type | Default | Description |
| :---------------------------------- | :------ | :----------------------- | :------------------ |
| `storageClass.create` | boolean | `true` | Create StorageClass |
| `storageClass.name` | string | `"gp3"` | StorageClass name |
| `storageClass.provisioner` | string | `"ebs.csi.aws.com"` | CSI provisioner |
| `storageClass.type` | string | `"gp3"` | EBS volume type |
| `storageClass.fsType` | string | `"ext4"` | File system type |
| `storageClass.reclaimPolicy` | string | `"Delete"` | Reclaim policy |
| `storageClass.volumeBindingMode` | string | `"WaitForFirstConsumer"` | Binding mode |
| `storageClass.allowVolumeExpansion` | boolean | `true` | Allow expansion |
For non-AWS clusters, set `storageClass.create: false` and ensure a compatible StorageClass exists. Provider-specific examples are in [Quick Start](/private-deployment/quick-start#cloud-specific-values).
## Decision Logs
/private-deployment/decision-logs
How decision logs flow through Kafka, Vector, object storage, and ClickHouse in a private Rulebricks deployment, and how to forward them elsewhere.
# Decision Logs
Every rule execution emits a structured decision log. The pipeline that stores and serves these logs is built around your shared object storage bucket.
## How the Pipeline Works
```mermaid
flowchart LR
HPS["HPS API / worker pods"] -->|produce| LogsTopic["Kafka logs topic"]
LogsTopic -->|consume| Vector[Vector]
Vector -->|"NDJSON archive (hourly partitions)"| Bucket["Object Storage (global.storage)"]
Bucket -->|query on demand| ClickHouse[ClickHouse]
ClickHouse --> Dashboard[Rulebricks Dashboard]
```
1. HPS API and worker pods produce decision log entries to the Kafka `logs` topic after each request completes (non-blocking, post-response). All solve traffic executes on HPS, so it owns the decision-log producer; the main app does not produce to Kafka.
2. Vector consumes the topic and writes the entries to your bucket under `global.storage.paths.decisionLogs` as compressed NDJSON, partitioned by year/month/day/hour.
3. ClickHouse queries the archive directly for decision logs. When ClickStack is enabled, the same ClickHouse deployment also stores operational telemetry locally, so persistence stays enabled by default.
4. The dashboard's native decision log search runs against ClickHouse. The archive remains plain NDJSON in your bucket, so you can also query it with your own tooling (Athena, DuckDB, and so on) or forward logs to additional sinks.
Bucket configuration and identity are covered in [Storage & Backups](/private-deployment/storage-and-backups).
## Logging Values
| Parameter | Type | Default | Description |
| :---------------------------------------- | :------ | :------------------ | :--------------------------------------------------------------------- |
| `rulebricks.app.logging.enabled` | boolean | `true` | Enable decision logging |
| `rulebricks.app.logging.kafkaBrokers` | string | `""` | Kafka brokers (auto-discovered if empty) |
| `rulebricks.app.logging.kafkaTopic` | string | `"logs"` | Kafka topic for logs |
| `rulebricks.app.logging.kafkaTopicPrefix` | string | `"com.rulebricks."` | Prefix applied to all Kafka topic names; set `""` to disable prefixing |
The topic prefix exists so Rulebricks topics don't collide on shared or managed Kafka clusters (for example, `com.rulebricks.logs`). HPS prepends it to its own topics, and the chart applies it to KEDA lag triggers and the Vector consumer so everything stays in sync. CLI-generated values set it to `""` for in-cluster Kafka, where the broker is dedicated.
If you run your own Kafka cluster, see [External Kafka & Redis](/private-deployment/external-services#vector-and-the-logs-topic) for how Vector connects to it and which ACLs it needs.
## Querying with ClickHouse
Rulebricks deploys ClickHouse to power native decision log querying in the dashboard. Decision log data lives in your bucket; ClickHouse queries that archive through a generated named collection. Local ClickHouse persistence is used for operational telemetry when ClickStack is enabled.
| Parameter | Type | Default | Description |
| :------------------------------- | :------ | :------------- | :-------------------------------------------------------------- |
| `clickhouse.enabled` | boolean | `true` | Deploy ClickHouse |
| `clickhouse.auth.username` | string | `"rulebricks"` | Query user |
| `clickhouse.persistence.enabled` | boolean | `true` | Keep local telemetry tables and query metadata across restarts |
| `clickhouse.persistence.size` | string | `"100Gi"` | Persistent volume size |
| `clickhouse.queryLimits.*` | object | see values | Memory, thread, row-read, and execution time caps |
| `clickhouse.otelQueryLimits.*` | object | see values | Separate limits for ClickStack telemetry queries |
ClickHouse reads the archive through a named collection generated from `global.storage.*`, so no separate storage configuration is needed.
## The Archive Sink
When `clickhouse.enabled` is on and `global.storage` is configured, the chart adds a `decision_logs` Vector sink that writes the NDJSON archive to your bucket. The key prefix uses an hourly partition layout (`year=YYYY/month=MM/day=DD/hour=HH/`) that must match what ClickHouse expects to query.
The CLI generates this sink automatically; hand-installs should leave the
generated partition layout intact, or ClickHouse queries will come back empty.
## Forwarding to Other Destinations
Vector can forward decision logs to additional destinations alongside the archive: SIEMs, data lakes, or HTTP endpoints. The chart templates Vector's `kafka` source automatically (brokers, TLS/SASL, and the prefixed log topic come from a generated `vector-kafka-env` ConfigMap), so you only add sinks under `vector.customConfig.sinks`. Use `normalize_logs` as the input to receive the same schema-normalized records the archive sink writes (raw `kafka` also works but skips normalization):
```yaml
vector:
customConfig:
sinks:
# Additional S3 sink example
s3:
type: aws_s3
inputs: [normalize_logs]
bucket: 'your-logs-bucket'
region: 'us-east-1'
key_prefix: 'rulebricks/logs/%Y/%m/%d/'
compression: gzip
encoding:
codec: json
```
See the [Vector sinks documentation](https://vector.dev/docs/reference/configuration/sinks/) for the full catalog of supported destinations.
## External Kafka & Redis
/private-deployment/external-services
Point the Rulebricks Helm chart at your own Kafka and Redis infrastructure, and understand why Supabase stays internal.
# External Kafka & Redis
This guide covers how to point the Rulebricks Helm chart at your own Kafka and Redis infrastructure instead of using the bundled instances. It is written for platform engineers deploying Rulebricks into a self-hosted Kubernetes environment.
Supabase externalization is not covered here. See the brief section on
[Supabase](#supabase) at the end of this document for the full rationale.
## How Rulebricks Uses These Services
For the overall component layout and request flow, see [Architecture](/private-deployment/architecture). The part that matters here: HPS (the rule execution server) uses a **correlation-ID request/response pattern** over Kafka. When a solve request arrives:
1. HPS produces a message to the `solution` Kafka topic with a unique correlation ID and a designated response partition.
2. A worker pod consumes the message, evaluates the rule, and produces the result to the `solution-response` topic on the exact partition the originating HPS replica is listening on.
3. HPS resolves the pending request and returns the result to the caller.
The idempotent Kafka producer guarantees exactly-once delivery at the broker level, and the correlation-ID mechanism ensures responses always route back to the correct HPS replica. This is why partition counts matter so much on an external cluster.
Redis sits in front of Supabase as a shared cache layer. API key authentication payloads are cached with a short TTL (60 seconds), while rule/flow definitions and named-environment lookups are cached without expiry and invalidated over a Redis pub/sub channel when they change; every pod also keeps an in-process LRU in front of Redis. In practice, Supabase sees very few direct queries under normal operation.
## Externalizing Kafka
### How Rulebricks Uses Kafka
Rulebricks uses three Kafka topics. Topic names carry the configured `rulebricks.app.logging.kafkaTopicPrefix` (default `com.rulebricks.`, so the actual topic is `com.rulebricks.solution` and so on). The prefix exists so Rulebricks topics don't collide on shared or managed clusters; set it to `""` to disable prefixing.
| Topic | Purpose | Producers | Consumers |
| :------------------ | :------------------------------------------------------ | :--------------- | :---------------------------------------------------- |
| `solution` | Inbound solve requests | HPS API pods | Worker pods (`generic-workers` consumer group) |
| `solution-response` | Outbound results routed back to originating HPS replica | Worker pods | HPS API pods (`hps-response-consumer` consumer group) |
| `logs` | Structured decision logs | HPS API and worker pods | Vector aggregator (`vector-consumers` consumer group) |
The `logs` topic is only truly optional if both `vector.enabled: false` and `rulebricks.app.logging.enabled: false`. In the default chart configuration the Vector pod is deployed and consumes this topic, so it needs to exist on your cluster. See [Vector and the logs topic](#vector-and-the-logs-topic) below.
Messages are plain JSON: no schema registry, no Kafka Connect, no custom broker plugins. The client library is KafkaJS, which speaks the standard Kafka wire protocol.
### Helm Values for External Kafka
To switch from the bundled Kafka to your own cluster, set the following in your values override:
```yaml
kafka:
enabled: false
rulebricks:
app:
logging:
enabled: true
kafkaBrokers: 'broker-1.example.com:9092,broker-2.example.com:9092'
```
The `kafkaBrokers` value drives every consumer in the stack:
- **HPS, workers, and the main app** receive it as the `KAFKA_BROKERS` environment variable via the shared ConfigMap. When `kafkaBrokers` is empty (the default), the chart auto-generates the internal cluster address `-kafka..svc.cluster.local:9092`.
- **Vector** is wired automatically through a templated `vector-kafka-env` ConfigMap that derives its bootstrap servers, TLS/SASL settings, and the prefixed log topic from the same `rulebricks.app.logging.*` values. You do not configure Vector's Kafka connection by hand. (For token-auth mechanisms, the ConfigMap points Vector at the local `kafkaBridge` proxy instead; see [Authentication](#authentication-sslsasl).)
- **KEDA**'s worker-scaling trigger also reads `kafkaBrokers` and points at your external cluster automatically (see [KEDA Autoscaling with External Kafka](#keda-autoscaling-with-external-kafka)).
### Authentication (SSL/SASL)
The chart supports TLS and SASL for external Kafka through `rulebricks.app.logging.kafkaSsl` and `kafkaSasl`, which it exposes to the app, HPS, workers, and Vector. Supported SASL mechanisms are `plain`, `scram-sha-256`, `scram-sha-512`, `aws-iam`, and `oauthbearer`.
Two consumers connect to Kafka with different auth capabilities: HPS (KafkaJS, which handles all supported mechanisms including token-based ones) and Vector (which natively supports SSL plus SASL `PLAIN`/`SCRAM`, but not token mechanisms like IAM or OAUTHBEARER). For token mechanisms, the chart provides a `kafkaBridge` kafka-proxy sidecar that authenticates upstream using the Vector pod's workload identity and exposes a local plaintext listener Vector consumes from. The CLI configures all of this automatically when you externalize Kafka; the examples below are for hand-installs.
**AWS MSK with IAM auth** (credentials from pod identity via IRSA, no static secrets):
```yaml
kafka:
enabled: false
rulebricks:
app:
logging:
enabled: true
kafkaBrokers: 'b-1.msk.example:9098,b-2.msk.example:9098'
kafkaSsl: true
kafkaSasl:
mechanism: 'aws-iam'
region: 'us-east-1'
hps:
serviceAccount:
create: true
annotations:
eks.amazonaws.com/role-arn: 'arn:aws:iam::ACCOUNT:role/msk-access'
# Vector cannot speak MSK IAM directly, so it uses the kafka-proxy bridge:
kafkaBridge:
enabled: true
provider: 'aws'
region: 'us-east-1'
brokers: 'b-1.msk.example:9098,b-2.msk.example:9098'
awsRoleArn: 'arn:aws:iam::ACCOUNT:role/msk-access'
vector:
serviceAccount:
create: true
annotations:
eks.amazonaws.com/role-arn: 'arn:aws:iam::ACCOUNT:role/msk-access'
```
**Azure Event Hubs (Kafka endpoint)** uses SASL PLAIN with the namespace connection string. Both HPS and Vector connect directly; no bridge sidecar required:
```yaml
kafka:
enabled: false
rulebricks:
app:
logging:
enabled: true
kafkaBrokers: 'my-namespace.servicebus.windows.net:9093'
kafkaSsl: true
kafkaSasl:
mechanism: 'plain'
username: '$ConnectionString'
password: 'Endpoint=sb://my-namespace.servicebus.windows.net/;SharedAccessKeyName=...;SharedAccessKey=...'
# or: existingSecret / existingSecretUsernameKey / existingSecretPasswordKey
```
**GCP Managed Service for Apache Kafka** prefers OAUTHBEARER with Workload Identity; the Vector bridge runs GCP's local auth-token server sidecar (`kafkaBridge.provider: "gcp"` with `gcpServiceAccountEmail`). A simpler `plain`/SCRAM credential also works for both consumers but uses static credentials.
### Topics to Pre-Create
The HPS producer sets `allowAutoTopicCreation: true`, but auto-created topics inherit the broker's default partition count, often just `1`. A single-partition `solution-response` will cause request timeouts under any meaningful replica count. **Always pre-create topics explicitly.**
In-cluster chart deployments handle all of this automatically with Strimzi `KafkaTopic` resources rendered from `kafka.topics`. For external managed Kafka, the chart can also create topics from the same list when `kafka.enabled: false` and `kafkaBridge.enabled: true`; otherwise topics are customer-managed.
Ask your Kafka team to create (names take your configured topic prefix):
| Topic | Partition Count | Replication | Retention |
| :------------------ | :---------------------------------------------------------------------- | :-------------------------------------- | :----------------------------------------------------------------- |
| `solution` | ~2x your maximum worker replica count (default 128) | 1 is acceptable (transient RPC traffic) | Short: `retention.ms=300000`, `segment.ms=300000`, small segments |
| `solution-response` | Must equal `rulebricks.hps.workers.solutionPartitions` | Same as `solution` | Same as `solution` |
| `logs` | 8-24 depending on volume | 2-3 in production (long-lived data) | Size to your Vector outage tolerance, e.g. `retention.ms=86400000` |
Set `max.message.bytes` to at least `2097152` (2 MB) on all three topics. HPS chunks are byte-bounded well below this, but the headroom prevents edge-case produce failures, and a response chunk can exceed its request chunk when rules expand payloads.
Partition count on `solution` is the worker fleet's concurrency ceiling, not a worker quota; the 2x headroom recommendation and the full sizing model are explained in [Performance & Scaling](/private-deployment/performance#the-sizing-model).
For replication, 1 is acceptable on the RPC topics because the traffic is transient and the HPS producer uses `acks=-1`, so higher replication adds ISR-wait latency to every request. Use 2+ only if broker loss must never produce a brief window of request failures.
### Partition Sizing for solution-response
The `solution-response` topic is partition-sensitive. HPS replicas share a consumer group over this topic, so each replica is assigned a subset of partitions. When producing a response, the worker writes to the _exact_ partition the originating HPS replica is consuming. If the topic has fewer partitions than expected, responses land on partitions no replica is watching, causing 30-second timeouts.
The expected partition count is set in the Helm values:
```yaml
rulebricks:
hps:
workers:
solutionPartitions: 128
```
The HPS deployment template passes `solutionPartitions` to HPS as the `MAX_WORKERS` environment variable.
Keep `solutionPartitions` equal to the actual partition count of
`solution-response` on your external cluster. A mismatch here is the most
common cause of 30-second timeouts on solve requests.
If you raise a topic's partition count later, HPS Kafka clients observe the change within about 30 seconds (`metadataMaxAge: 30000`), and consumers fully rebalance onto new partitions at their next group rebalance. Chart upgrades roll the workers, which forces one. HPS pods gate readiness on the response consumer owning partitions (`GET /ready` returns 503 until group join completes), so traffic never reaches an instance that can't receive its responses.
### ZooKeeper vs KRaft
HPS does not care how your Kafka cluster manages metadata. It never connects to ZooKeeper directly; it only speaks the Kafka wire protocol to brokers via a bootstrap address. Both ZooKeeper-backed clusters and KRaft-mode clusters work identically. Managed services like AWS MSK (in either mode), Confluent Cloud, Aiven, and Redpanda are all compatible.
The bundled Kafka deployment is managed by Strimzi and runs in KRaft mode. That is an internal broker implementation detail and has no bearing on external cluster compatibility.
### Tuning and Idempotency
The HPS Kafka client is pre-tuned for a low-latency request/response workload. These settings are baked into the application and do not need external configuration, but are worth understanding:
**Producer:**
- **Idempotent mode** (`idempotent: true`, `acks: -1`) guarantees exactly-once produce semantics per session. If a network blip causes a retry, Kafka deduplicates the message at the broker. This is Kafka-level idempotency, not HTTP-level: a client retrying an HTTP request produces a new message with a new correlation ID.
- **Snappy compression** reduces wire bytes. The broker must support Snappy (enabled by default on Apache Kafka and Confluent). If your cluster has disabled Snappy, contact Rulebricks support.
- **`lingerMs: 0`** sends immediately. Latency is prioritized over batch throughput because these are synchronous, user-facing requests.
**Consumer:**
- The HPS response consumer uses **`sessionTimeout: 60000`, `heartbeatInterval: 15000`**, tolerating brief idle periods without spurious rebalancing.
- Workers use a tighter **30s session timeout with a 3s heartbeat** (tunable via `WORKER_SESSION_TIMEOUT_MS` / `WORKER_HEARTBEAT_INTERVAL_MS`) so a hung worker releases its partitions quickly.
- **`maxWaitTimeInMs: 50`** makes the broker return fetched messages quickly for low-latency response delivery.
- Workers process up to 4 partitions concurrently per pod. Throughput scales by adding worker replicas, not by raising concurrency.
No tuning is needed on the external Kafka side beyond ensuring the three topics exist with correct partition counts and reasonable ISR settings (e.g., `min.insync.replicas=1` for single-broker, `2` for multi-broker production).
### Request Size Limits
Admission is byte-first; item counts are not limited by default. Useful when setting client expectations:
- **Total request body:** 6 MiB hard ceiling by default (HTTP 413 above it; configurable via `HTTP_BODY_LIMIT_BYTES` on HPS). We recommend clients stay around 1 MB or less for the best latency profile. Larger bodies execute fine but cost proportionally more parse time and fan out into more chunks.
- **Single payload:** 1.5 MiB serialized hard ceiling (HTTP 413 naming the offending array index; configurable via `ITEM_MAX_BYTES`, which must stay below the topics' `max.message.bytes` minus envelope headroom).
- **Item count:** unlimited by default. Operators can opt into a cap with `BULK_MAX_ITEMS` (HTTP 400 above it).
- **Response amplification:** each chunk's response must fit the topic's `max.message.bytes` (2 MB default). Rules that expand outputs beyond roughly 16 KB average per payload will fail the request with an explicit error.
- The whole request shares one 30-second execution deadline regardless of size.
### KEDA Autoscaling with External Kafka
The KEDA ScaledObject for HPS workers monitors consumer lag on the `solution` topic. When you externalize Kafka, KEDA's `bootstrapServers` is automatically derived from your `kafkaBrokers` value. No additional KEDA configuration is needed.
However, ensure KEDA can reach your external Kafka brokers from within the cluster (network policies, VPC peering, security groups, etc.). If your Kafka requires SASL, KEDA's Kafka trigger also needs authentication; see the [KEDA Kafka trigger docs](https://keda.sh/docs/latest/scalers/apache-kafka/) for details.
### Vector and the logs topic
The Rulebricks chart ships a Vector pod by default as the consumer of the `logs` topic. HPS API and worker pods produce structured decision-log entries to Kafka after each request completes (non-blocking, post-response); Vector reads them and forwards to whatever sink the chart is configured for, commonly the object storage archive that ClickHouse queries, S3, or an HTTP endpoint into a SIEM.
A few things to know when externalizing Kafka:
- **Consumer group.** Vector joins Kafka as `vector-consumers` (configured in `vector.customConfig.sources.kafka.group_id`). This is the group ID to use in `kafka-consumer-groups.sh` commands and any ACL rules.
- **Consume-only.** In the default chart configuration Vector only reads from the `logs` topic; it does not produce back to Kafka. If your cluster enforces ACLs, the Vector principal needs `Read` and `Describe` on `logs` with group id `vector-consumers`, and no `Write`.
- **Network reachability.** Vector must be able to reach the external brokers from inside the cluster, the same as HPS and the workers. Check network policies, security groups, and VPC peering accordingly.
To verify Vector is healthy after pointing it at external Kafka:
```bash
kubectl get pods | grep vector
kubectl logs | grep -i "kafka\|partition"
kafka-consumer-groups.sh --bootstrap-server \
--describe --group vector-consumers
```
The pod should be `Running`, the logs should show a successful Kafka connection and partition assignment at startup, and consumer-group lag on `logs` should stay near zero under normal load. Growing lag almost always means Vector cannot reach the brokers or is missing ACL permissions.
### Disabling Kafka Entirely
Setting `kafka.enabled: false` without providing external `kafkaBrokers` effectively disables HPS. All solve endpoints return HTTP 503. Only the `/health` endpoint continues to respond. This is only useful for running a control-plane-only deployment (dashboard and admin APIs without rule execution).
It also breaks the decision-log pipeline: Vector loses its data source, so any downstream sink (ClickHouse, S3, SIEM) will see a complete gap for the duration of the Kafka outage. See [Vector and the logs topic](#vector-and-the-logs-topic) for the consumer-side details.
## Externalizing Redis
### How Rulebricks Uses Redis
Redis serves as a shared cache between all Rulebricks components: the main app, HPS, and workers. It sits in a three-tier caching hierarchy:
1. **L1: In-process LRU** (per pod, always present)
2. **L2: Redis** (shared across all pods and replicas)
3. **L3: Supabase** (source of truth, queried on full cache miss)
What lives in Redis:
| Data | TTL | Written By |
| :--------------------------------------- | :------------------------------- | :----------------- |
| API key auth payloads | 60s | HPS |
| Rule/flow definitions (compressed) | No expiry (pub/sub invalidation) | Main app (not HPS) |
| Named-environment release mappings | No expiry | Main app (not HPS) |
| Flow node results (API, SOAP, DB, vault) | 1–300s | Workers |
Redis is accessed using basic commands (`GET`, `SET` with `EX`, `EXPIRE`, `DEL`) plus one pub/sub channel: the main app publishes to `rulebricks:cache:invalidate` after rule, flow, or API-key changes, and HPS and worker pods subscribe to clear their in-process caches immediately. No Lua scripts, no streams. A vanilla Redis instance is all that's needed, with no clustering, persistence configuration, or eviction policy requirements — but the endpoint must support `PUBLISH`/`SUBSCRIBE`. If pub/sub is unavailable, pods log a warning and cache freshness degrades to TTL-based expiry only.
### What You Provide
To use your own Redis instance, you provide **one thing**: a Redis host and port.
```yaml
rulebricks:
redis:
enabled: false
external:
host: 'your-redis.example.com'
port: 6379
password: 'your-password'
# existingSecret: "my-redis-secret"
# existingSecretKey: "redis-password"
tls:
enabled: false
```
Setting `redis.enabled: false` stops the chart from deploying its own internal Redis pod and PVC. The chart takes care of everything else; all internal components are wired to your Redis automatically.
### What the Chart Does Behind the Scenes
The Rulebricks stack has two types of Redis consumers:
- **HPS and workers** connect to Redis directly via the native Redis protocol (`ioredis`). This is the fast path (~1–2ms per operation) and is configured via the `REDIS_URL` environment variable, which the chart constructs from your `external.host` and `external.port`.
- **The main app** (Next.js) uses an HTTP-based Redis client (`@vercel/kv`). It cannot connect to Redis natively, so it needs an HTTP translation layer.
To bridge this, the chart always deploys a lightweight internal proxy (`serverless-redis-http`) that speaks HTTP on one side and the Redis protocol on the other. When you externalize Redis, this proxy is automatically pointed at your external instance. You do not need to configure or think about it; it's an internal implementation detail.
In short: you provide a standard Redis endpoint, and the chart handles routing each component to it through the appropriate protocol.
### Connection Examples
| Provider | `host` | `port` | `tls.enabled` |
| :------------------------------ | :-------------------------------------------------- | :------ | :------------ |
| AWS ElastiCache (no TLS) | `primary.my-cluster.use1.cache.amazonaws.com` | `6379` | `false` |
| AWS ElastiCache (TLS) | `primary.my-cluster.use1.cache.amazonaws.com` | `6379` | `true` |
| AWS MemoryDB | `clustercfg.my-cluster.use1.memorydb.amazonaws.com` | `6379` | `true` |
| Redis Cloud | `redis-12345.c1.us-east-1.redns.redis-cloud.com` | `12345` | `true` |
| GCP Memorystore | `10.x.x.x` | `6379` | `false` |
| Self-hosted (another namespace) | `redis-svc.other-ns.svc.cluster.local` | `6379` | `false` |
The Redis client is pre-configured with TCP keepalive, auto-pipelining, and exponential-backoff retries. No client-side tuning is typically required.
### Disabling Redis Entirely
If `redis.enabled` is `false` and no `external` host is provided, Redis operates in a no-op mode where cache writes are silently discarded and reads return empty. HPS will start and log a warning, but consequences are significant:
- **Every authentication and entity lookup hits Supabase directly.** Expect a ~60x increase in database queries at steady state.
- **Named-environment URLs stop working.** Requests like `/api/v1/solve/my-rule/prod` return 404 because the `releases_*` cache keys are only written by the main app into Redis. Numeric versions and `latest` still work.
- **Flow-node caching is lost.** API, SOAP, and database nodes inside flows execute on every invocation.
## Supabase
The Supabase stack itself stays in-cluster (or on Supabase Cloud); what you can externalize is the PostgreSQL server underneath it. Set `supabase.externalDatabase.enabled: true` and configure `supabase.externalDatabase.*` to point the Supabase services at a server you manage. See [External PostgreSQL](/private-deployment/configuration-reference#external-postgresql) for the values.
The full self-hosted Supabase stack includes the following services:
- **PostgreSQL** stores users, teams, rules, flows, API keys, usage data, and all application state.
- **GoTrue (Auth)** handles user signup, login, password recovery, email verification, SSO/OIDC, and JWT issuance. The main app delegates all authentication to Supabase Auth.
- **PostgREST** provides the REST API layer over PostgreSQL that the application queries.
- **Realtime** powers live-update features in the dashboard.
- **Kong** is the API gateway that routes and authenticates requests to the above services.
These services stay chart-managed even with an external database. Beyond providing a clear database layer for Rulebricks, Supabase is our interface for authentication, JWT management, and real-time features. There are database triggers between Supabase-managed identity tables (e.g., `auth.users`) and Rulebricks-managed application tables that depend on Supabase's internal wiring, so the external server must be a vanilla PostgreSQL that the Supabase services can fully manage.
### Supported PostgreSQL Versions
For an external database, run **PostgreSQL 17** (recommended — it matches the bundled in-cluster database and the CLI's managed-RDS default) or **PostgreSQL 16**. PostgreSQL 15 and below are not supported: the bootstrap job's replication grants require PostgreSQL 16+ role-inheritance syntax on AWS RDS, and Supabase Realtime does not support older majors without superuser access, which managed providers do not offer. The server must also have logical replication enabled (`wal_level = logical`; on RDS set `rds.logical_replication = 1` in the parameter group) for Realtime.
It is worth noting **the database is likely not a bottleneck.** Because Redis aggressively caches entity definitions, and every pod has an in-process LRU in front of Redis, actual database read/write volumes under normal operation are generally minimal.
### Database Backups
While the Supabase PostgreSQL instance is lightweight, it holds all application state and should be backed up regularly if self-hosting. The chart manages this for you with scheduled logical backups (`pg_dump`/`pg_dumpall` uploaded to shared object storage), plus on-demand backup and restore through the CLI. See [Storage & Backups](/private-deployment/storage-and-backups).
## Verification Checklist
After switching to external Kafka or Redis, verify the deployment is healthy:
### 1. HPS health endpoints
HPS exposes two endpoints: `/health` (process-alive, used by the liveness probe) and `/ready` (used by the readiness probe; returns 503 until the Kafka response consumer has joined its group and owns partitions, so pods never receive traffic before they can route responses).
```bash
kubectl port-forward svc/-hps 3000:3000
curl -s http://localhost:3000/health | jq
curl -si http://localhost:3000/ready | head -1
```
Expected from `/health`:
```json
{
"status": "ok",
"redis": true,
"cacheInvalidation": {
"enabled": true,
"ready": true,
"channel": "rulebricks:cache:invalidate"
},
"kafka": {
"enabled": true,
"ready": true,
"partitions": 21
}
}
```
- `redis: true` confirms the KV client connected. If `false`, check your `REDIS_URL` or `redis.external.*` values.
- `cacheInvalidation.ready: true` confirms the pod's pub/sub subscription against your Redis. If `false`, the endpoint likely does not support `SUBSCRIBE` (or the connection failed) and cache freshness falls back to TTL-based expiry.
- `kafka.ready: true` confirms the consumer joined its group. If `false` on startup, retry after 10 to 15 seconds; group join can take a moment. `/ready` returns 200 once this completes.
- `kafka.partitions` shows how many `solution-response` partitions this pod owns. In a 3-replica HPS against a 64-partition topic, each pod should report ~21.
### 2. End-to-end smoke test
```bash
curl -X POST http://localhost:3000/api/v1/solve//latest \
-H "x-api-key: $API_KEY" \
-H "content-type: application/json" \
-d '{"input": "value"}'
```
A 200 response confirms Kafka, Redis, Supabase, and the worker pipeline are all healthy.
### 3. Common failure modes
| Symptom | Likely Cause | Fix |
| :--------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- |
| All solve requests return 503 | Kafka disabled or unreachable | Check `kafkaBrokers`, verify network connectivity to brokers |
| Requests timeout after 30s | `solution-response` partition count ≠ `solutionPartitions` | Raise the topic's partition count, or update `solutionPartitions` to match |
| Named-environment URLs return 404 | Redis not connected, or main app and HPS point at different Redis instances | Verify `redis: true` on `/health`; ensure shared Redis |
| Persistent consumer lag on `solution` | Not enough worker replicas | Scale up `hps.workers.replicas` or `hps.workers.keda.minReplicaCount` |
| Decision logs stop reaching ClickHouse / S3 / SIEM after externalizing Kafka | Vector can't reach or authenticate to the external broker | Check Vector pod logs and network reachability; for token-auth Kafka verify the `kafkaBridge` sidecar is enabled |
## Metrics Reference
/private-deployment/metrics-reference
Reference for Rulebricks private deployment metrics, labels, meanings, and useful PromQL queries.
# Metrics Reference
This page lists the primary metrics exported by a private Rulebricks deployment. It focuses on metrics owned by Rulebricks services plus the optional exporters the Helm chart can deploy. Kubernetes, node, container, and PVC metrics come from `kube-prometheus-stack` and use standard Prometheus ecosystem names.
## Label Policy
Rulebricks labels are intentionally bounded. Metrics use route templates, status classes, coarse reasons, operation names, result states, and backend names. They do **not** include API keys, users, organizations, IP addresses, raw URLs, rule slugs, flow slugs, request payloads, or error messages.
## App Metrics
Served at `/api/metrics` by the Rulebricks app. App metrics are only exposed on self-hosted deployments (the endpoint is gated on the `SELF_HOSTED` environment variable, which the chart sets automatically).
| Metric | Type | Labels | Meaning |
| :----- | :--- | :----- | :------ |
| `rulebricks_app_http_requests_total` | counter | `route`, `method`, `status_class` | App/API request count. |
| `rulebricks_app_http_request_duration_seconds` | histogram | `route`, `method`, `status_class` | App/API request duration. |
| `rulebricks_app_http_rejections_total` | counter | `route`, `reason` | App/API rejected requests by coarse reason. |
| `rulebricks_app_frontend_errors_total` | counter | `source` | Browser-side errors reported by the app. |
| `rulebricks_app_redis_operations_total` | counter | `operation`, `result`, `backend` | Self-hosted app Redis/Valkey operations. |
| `rulebricks_app_redis_operation_duration_seconds` | histogram | `operation`, `result`, `backend` | Self-hosted app Redis/Valkey operation latency. |
App Redis metrics are self-hosted only. They cover app cache reads/writes/deletes/expirations and cache invalidation publishes. Filament build quota counters are intentionally excluded.
Useful queries:
```promql
sum by (status_class) (rate(rulebricks_app_http_requests_total[5m]))
histogram_quantile(0.95, sum by (le, route) (rate(rulebricks_app_http_request_duration_seconds_bucket[5m])))
sum by (operation, result) (rate(rulebricks_app_redis_operations_total[5m]))
histogram_quantile(0.95, sum by (le, operation) (rate(rulebricks_app_redis_operation_duration_seconds_bucket[5m])))
```
## HPS Metrics
Served at `/metrics` by HPS.
| Metric | Type | Labels | Meaning |
| :----- | :--- | :----- | :------ |
| `rulebricks_hps_http_requests_total` | counter | `route`, `method`, `status_class` | HPS request count for solve, bulk solve, flows, and parallel solve routes. |
| `rulebricks_hps_http_request_duration_seconds` | histogram | `route`, `method`, `status_class` | End-to-end HPS HTTP duration. |
| `rulebricks_hps_rejections_total` | counter | `route`, `reason` | HPS rejected requests by coarse reason. |
| `rulebricks_hps_kafka_request_duration_seconds` | histogram | `operation` | Kafka-backed request/response duration observed by HPS. |
| `rulebricks_hps_kafka_errors_total` | counter | `operation`, `reason` | Kafka-backed request errors. |
| `rulebricks_hps_bulk_items_total` | counter | `operation` | Total bulk or parallel payload items accepted by HPS. |
| `rulebricks_hps_decision_log_failures_total` | counter | `reason` | Decision-log records that failed to produce to Kafka. |
| `rulebricks_hps_decision_logs_total` | counter | `operation`, `result` | Decision-log records attempted by HPS. |
| `rulebricks_hps_decision_log_bytes_total` | counter | `operation` | Serialized decision-log bytes successfully produced to Kafka. |
| `rulebricks_hps_chunks_per_request` | histogram | `operation` | Kafka chunks emitted per request. |
| `rulebricks_hps_chunk_failures_total` | counter | `operation`, `reason` | Chunk-level failures in chunked bulk requests. |
| `rulebricks_hps_chunk_processing_ms` | histogram | `operation` | Worker processing time reported per chunk. |
| `rulebricks_hps_chunk_cost_ms_per_item` | gauge | `operation` | Learned chunk planner cost per item. |
| `rulebricks_hps_chunk_cost_ms_per_byte` | gauge | `operation` | Learned chunk planner cost per input byte. |
| `rulebricks_hps_cache_items` | gauge | `state` | In-process HPS memory cache item counts. |
| `rulebricks_hps_cache_max_entries` | gauge | none | HPS memory cache capacity. |
| `rulebricks_hps_cache_requests_total` | counter | `result` | HPS memory cache hits and misses. |
| `rulebricks_hps_redis_cache_operations_total` | counter | `operation`, `result`, `backend` | HPS shared Redis/Valkey operations. |
| `rulebricks_hps_redis_cache_operation_duration_seconds` | histogram | `operation`, `result`, `backend` | HPS shared Redis/Valkey operation latency. |
Useful queries:
```promql
sum by (route, status_class) (rate(rulebricks_hps_http_requests_total[5m]))
histogram_quantile(0.95, sum by (le, route) (rate(rulebricks_hps_http_request_duration_seconds_bucket[5m])))
histogram_quantile(0.95, sum by (le, operation) (rate(rulebricks_hps_kafka_request_duration_seconds_bucket[5m])))
sum by (operation, result) (rate(rulebricks_hps_decision_logs_total[5m]))
sum by (operation) (rate(rulebricks_hps_decision_log_bytes_total[5m]))
sum by (operation, result) (rate(rulebricks_hps_redis_cache_operations_total[5m]))
```
## Worker Metrics
Served at `/metrics` by HPS worker pods.
| Metric | Type | Labels | Meaning |
| :----- | :--- | :----- | :------ |
| `rulebricks_worker_messages_total` | counter | `operation`, `status` | Worker Kafka messages processed. |
| `rulebricks_worker_processing_duration_seconds` | histogram | `operation`, `status` | Worker processing duration. |
| `rulebricks_worker_consumer_up` | gauge | none | 1 while the worker's Kafka consumer is running, 0 after a fatal crash until recovery succeeds. |
| `rulebricks_worker_redis_cache_operations_total` | counter | `operation`, `result`, `backend` | Worker Redis/Valkey operations. |
| `rulebricks_worker_redis_cache_operation_duration_seconds` | histogram | `operation`, `result`, `backend` | Worker Redis/Valkey operation latency. |
Useful queries:
```promql
sum by (operation, status) (rate(rulebricks_worker_messages_total[5m]))
histogram_quantile(0.95, sum by (le, operation) (rate(rulebricks_worker_processing_duration_seconds_bucket[5m])))
sum by (operation, result) (rate(rulebricks_worker_redis_cache_operations_total[5m]))
```
## Default Node.js and Process Metrics
App, HPS, and workers also export default `prom-client` process metrics with service-specific prefixes:
- `rulebricks_app_*`
- `rulebricks_hps_*`
- `rulebricks_worker_*`
Common examples:
| Metric suffix | Meaning |
| :------------ | :------ |
| `process_cpu_seconds_total` | Process CPU time. |
| `process_resident_memory_bytes` | Resident memory. |
| `process_open_fds` | Open file descriptors. |
| `nodejs_heap_size_used_bytes` | V8 heap used. |
| `nodejs_eventloop_lag_p99_seconds` | Event loop lag p99. |
| `nodejs_gc_duration_seconds` | Garbage collection duration histogram. |
## Valkey / Redis Exporter Metrics
Requires `rulebricks.cache.redisExporter.enabled`.
| Metric | Meaning |
| :----- | :------ |
| `redis_commands_processed_total` | Total Valkey commands processed. |
| `redis_connected_clients` | Connected client count. |
| `redis_memory_used_bytes` | Current memory used. |
| `redis_memory_max_bytes` | Configured max memory when available. |
| `redis_keyspace_hits_total` | Keyspace cache hits. |
| `redis_keyspace_misses_total` | Keyspace cache misses. |
| `redis_evicted_keys_total` | Evicted keys. |
Useful queries:
```promql
sum(rate(redis_commands_processed_total[5m]))
redis_memory_used_bytes
redis_connected_clients
sum(rate(redis_keyspace_hits_total[5m])) / (sum(rate(redis_keyspace_hits_total[5m])) + sum(rate(redis_keyspace_misses_total[5m])))
sum(rate(redis_evicted_keys_total[5m]))
```
## Kafka Metrics
Kafka JMX metrics are enabled by the Kafka chart. Kafka consumer-group lag requires `rulebricks.kafkaExporter.enabled`.
| Metric | Source | Meaning |
| :----- | :----- | :------ |
| `kafka_server_brokertopicmetrics_messagesin_total` | Kafka JMX | Broker messages in. |
| `kafka_server_brokertopicmetrics_bytesin_total` | Kafka JMX | Broker bytes in. |
| `kafka_server_brokertopicmetrics_bytesout_total` | Kafka JMX | Broker bytes out. |
| `kafka_network_requestchannel_requestqueuesize` | Kafka JMX | Broker request queue size. |
| `kafka_network_requestchannel_responsequeuesize` | Kafka JMX | Broker response queue size. |
| `kafka_log_log_size` | Kafka JMX | Topic log size. |
| `kafka_consumergroup_lag` | Kafka exporter | Consumer-group lag by group/topic/partition. |
Useful queries:
```promql
sum by (consumergroup, topic) (kafka_consumergroup_lag)
sum(rate(kafka_server_brokertopicmetrics_messagesin_total[5m]))
sum by (topic) (kafka_log_log_size)
```
## Traefik Metrics
Traefik metrics are scraped when the Traefik ServiceMonitor is active.
| Metric | Meaning |
| :----- | :------ |
| `traefik_service_requests_total` | Requests by Traefik service/status. |
| `traefik_service_request_duration_seconds` | Request duration histogram by service. |
| `traefik_entrypoint_open_connections` | Open connections by entrypoint/protocol. |
| `traefik_entrypoint_requests_total` | Entrypoint request count. |
| `traefik_config_reloads_total` | Traefik config reloads. |
Useful queries:
```promql
sum by (service, code) (rate(traefik_service_requests_total[5m]))
histogram_quantile(0.95, sum by (le, service) (rate(traefik_service_request_duration_seconds_bucket[5m])))
sum by (entrypoint, protocol) (traefik_entrypoint_open_connections)
```
## Kubernetes and Infrastructure Metrics
These come from kube-prometheus-stack and are cloud-portable:
| Metric family | Meaning |
| :------------ | :------ |
| `kube_deployment_*` | Desired, available, unavailable deployment replicas. |
| `kube_horizontalpodautoscaler_*` | HPA current, desired, min, and max replicas. |
| `kube_pod_*` | Pod phase, labels, ownership, restarts, readiness. |
| `kube_node_*` | Node labels, allocatable resources, readiness. |
| `container_cpu_usage_seconds_total` | Container CPU usage from cAdvisor. |
| `container_memory_working_set_bytes` | Container memory working set. |
| `container_cpu_cfs_throttled_periods_total` | CPU throttling periods. |
| `node_cpu_seconds_total` | Node CPU time from node-exporter. |
| `node_memory_*` | Node memory from node-exporter. |
| `node_filesystem_*` | Node filesystem capacity/usage. |
| `kubelet_volume_stats_*` | PVC volume capacity and usage. |
Useful queries:
```promql
100 * (1 - avg by (node) (rate(node_cpu_seconds_total{mode="idle"}[5m])))
100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))
100 * kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes
kube_horizontalpodautoscaler_status_desired_replicas
kube_deployment_status_replicas_available
```
## ClickHouse Metrics
ClickHouse exposes a large metric surface. The Rulebricks dashboard uses a small set of high-level signals to avoid noise:
| Metric | Meaning |
| :----- | :------ |
| `ClickHouseMetrics_Query` | Active ClickHouse queries. |
| `ClickHouseMetrics_MemoryTracking` | Memory tracked by ClickHouse. |
| `ClickHouseProfileEvents_Query` | Query event count. |
Use ClickHouse's own dashboards or direct ClickHouse queries when investigating storage/query internals in detail.
## Alert Starting Points
These are good first alerts for production:
```promql
sum(rate(rulebricks_hps_rejections_total[5m])) > 0
histogram_quantile(0.95, sum by (le) (rate(rulebricks_hps_kafka_request_duration_seconds_bucket[5m]))) > 5
sum by (consumergroup, topic) (kafka_consumergroup_lag) > 1000
100 * kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 85
sum(rate(rulebricks_hps_decision_log_failures_total[5m])) > 0
```
## Monitoring
/private-deployment/monitoring
Prometheus monitoring for private Rulebricks deployments, including scraped metrics, label policy, and remote write configuration.
# Monitoring
Monitoring is enabled by default through `kube-prometheus-stack`. Prometheus stores metrics in-cluster unless you configure a remote write destination.
ClickStack is also enabled by default for operational logs, traces, and a HyperDX UI. It complements Prometheus rather than replacing it: Prometheus remains the metrics source, while ClickStack handles trace and log exploration for the deployment.
| Parameter | Type | Default | Description |
| :------------------------------------------- | :------ | :------ | :------------------------- |
| `monitoring.enabled` | boolean | `true` | Enable monitoring |
| `rulebricks.metrics.enabled` | boolean | `true` | Rulebricks ServiceMonitors |
| `kube-prometheus-stack.alertmanager.enabled` | boolean | `false` | Deploy Alertmanager |
| `kube-prometheus-stack.grafana.enabled` | boolean | `false` | Deploy Grafana |
| `global.clickstack.enabled` | boolean | `true` | Enable built-in ClickStack |
## What's Scraped
The chart adds ServiceMonitors for:
- **App** (`/api/metrics`): app/admin API request counts, latency histograms, coarse rejections, and frontend error counts.
- **HPS** (`/metrics`): rule-engine request counts, latency histograms, rejections, Kafka worker wait time, bulk item volume, memory cache stats, HPS-side Redis/Valkey operations, and decision-log produce throughput.
- **HPS workers** (`/metrics`): per-worker message throughput (`rulebricks_worker_messages_total`), processing-time histograms (`rulebricks_worker_processing_duration_seconds`), and worker Redis/Valkey operations by operation/result/backend, scraped from the headless worker Service.
- **Supporting infrastructure** where available: Kafka JMX, ClickHouse metrics, and Traefik edge metrics.
Metric labels are intentionally bounded to avoid cardinality problems: route templates, methods, status classes, operations, and coarse reasons. They never include API keys, users, organizations, IP addresses, raw URLs, rule slugs, flow slugs, or error messages.
Useful queries:
```promql
histogram_quantile(0.95, sum(rate(rulebricks_hps_http_request_duration_seconds_bucket[5m])) by (le, route))
sum(rate(rulebricks_hps_rejections_total[5m])) by (route, reason)
histogram_quantile(0.95, sum(rate(rulebricks_hps_kafka_request_duration_seconds_bucket[5m])) by (le, operation))
sum(rate(rulebricks_hps_bulk_items_total[5m])) by (operation)
sum(rate(rulebricks_app_frontend_errors_total[5m])) by (source)
```
After install, verify scrape discovery:
```bash
kubectl get servicemonitor -n rulebricks
kubectl port-forward -n rulebricks svc/rulebricks-kube-prometheus-stack-prometheus 9090:9090
```
## Signals at a Glance
Prometheus captures the main operational signals you need to run Rulebricks:
- **Traffic and latency** for the app, HPS, workers, and Traefik.
- **Queue health** for Kafka request/response topics and consumer groups.
- **Cache health** from app/HPS/worker Redis operation metrics plus Valkey backend metrics.
- **Decision-log pipeline health** from HPS, Vector, and ClickHouse metrics.
- **Infrastructure health** from Kubernetes, node, container, HPA, and PVC metrics.
For the complete list of metric names, labels, meanings, useful PromQL, and alert starting points, see [Metrics Reference](/private-deployment/metrics-reference).
## Exporters
The chart enables the standard exporters needed for the dashboard where it can do so safely:
- `rulebricks.cache.redisExporter.enabled` exports Valkey metrics such as memory, clients, hit ratio, evictions, and operations per second.
- `rulebricks.kafkaExporter.enabled` exports Kafka topic and consumer-group lag for embedded Kafka.
- Traefik's ServiceMonitor exports ingress request rate, status codes, latency, and connection metrics.
App-side Redis metrics are self-hosted only. They cover app cache reads,
writes, deletes, expirations, and cache-invalidation publishes, but intentionally
exclude Filament build quota counters. Contexts runtime state is included under
ordinary app Redis `get`/`set`/`del` operations.
## Remote Write
To ship metrics to AWS Managed Prometheus, Azure Monitor managed Prometheus, Grafana Cloud, or another remote-write-compatible backend:
```yaml
kube-prometheus-stack:
prometheus:
prometheusSpec:
remoteWrite:
- url: 'https://prometheus-prod-XX.grafana.net/api/prom/push'
basicAuth:
username:
name: prometheus-remote-write
key: username
password:
name: prometheus-remote-write
key: password
```
The [Rulebricks CLI](/private-deployment/cli) wizard asks for a remote write
destination during `rulebricks init` and generates this block for you. You can
skip that step and add it later.
## Cloud-native Logs
Rulebricks does not deploy provider-specific log agents for Azure Monitor or
Amazon CloudWatch Logs. On managed Kubernetes, use the platform collector:
- **AKS**: Azure Container Insights / Azure Monitor Agent collects pod stdout
into Log Analytics (`ContainerLogV2`).
- **EKS**: the Amazon CloudWatch Observability EKS add-on collects pod logs into
CloudWatch Logs using Fluent Bit or the OTel Container Insights pipeline.
The app, HPS, and worker emit structured JSON service logs with `trace_id` and
`span_id`, so these cloud-native collectors can correlate logs with traces
without Rulebricks shipping a cloud-specific Vector sink.
## Grafana Dashboards
Enabling in-cluster Grafana (the CLI's **local-grafana** monitoring destination, or `kube-prometheus-stack.grafana.enabled: true`) provisions a **Rulebricks Overview** dashboard automatically via the Grafana dashboard sidecar. The dashboard is shipped as a labeled ConfigMap (`grafana_dashboard: "1"`), so the sidecar imports it on startup with no manual upload. You can also import the same JSON into a customer-managed Grafana or Azure Managed Grafana instance.
The dashboard is organized as an operational cockpit:
- **Executive Health**: total app/HPS request rate, error/rejection rate, HPS latency, Kafka RPC latency, worker latency, Kafka lag, and Valkey health.
- **Ingress / Traefik**: ingress request rate by service/status, Traefik p95 service latency, open connections, entrypoint traffic, TLS certificate metadata, and config reloads.
- **HPS Request Plane**: route/status throughput, route latency, Kafka RPC latency, Kafka errors, bulk payload throughput, chunk counts, and chunk failures.
- **Worker Fleet**: worker throughput, processing quantiles, CPU/memory by worker pod, restarts, and OOM events.
- **App + API Surface**: app request rate and latency, frontend/app rejection rates, app Redis operation rate and latency, event-loop lag, and GC duration.
- **Cache / Valkey**: service-level app/HPS/worker Redis operations plus backend Valkey ops, memory, clients, hit ratio, misses, and evictions.
- **Kafka / Queue Health**: consumer-group lag, broker traffic, request queues, topic disk usage, and produce/fetch errors.
- **Decision Logs / Logging Pipeline**: decision-log produce rate, bytes, failures, Vector pod resources, and selected ClickHouse health.
- **Infrastructure / Nodes / Node Pools**: node CPU, memory, filesystem usage, pod CPU/memory by Rulebricks workload group, CPU throttling, and PVC usage.
- **Scaling / Scheduling**: HPA current/desired/max replicas, deployment availability, pending/unschedulable pods, restarts, and node allocatable resources.
Most panels use kube-prometheus-stack defaults (`kube-state-metrics`, node-exporter, kubelet/cAdvisor) and are cloud-portable. A few panels depend on optional exporters:
- **Valkey backend panels** require `rulebricks.cache.redisExporter.enabled`.
- **Kafka consumer-group lag panels** require `rulebricks.kafkaExporter.enabled`.
Node-pool views are best-effort because AKS, EKS, GKE, and self-managed clusters expose different node labels. The dashboard defaults to node-level infrastructure panels and only groups by common labels where the metric exists.
Grafana is the pane of glass for **metrics**. For **traces** and **logs** (request lineage), see [Distributed Tracing](/private-deployment/tracing) - viewed in the bundled ClickStack/HyperDX UI by default, or forwarded to a backend of your choice (Elastic APM, a generic OTLP backend, or Azure Monitor).
## In-Cluster Retention
When keeping metrics in-cluster, give Prometheus persistent storage sized for your retention window:
```yaml
kube-prometheus-stack:
prometheus:
prometheusSpec:
retention: 30d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 50Gi
```
## Performance & Scaling
/private-deployment/performance
How rule execution capacity scales in a private Rulebricks deployment, and which values to tune for higher throughput.
# Performance & Scaling
Rule execution capacity is determined by the HPS worker fleet. This page explains the sizing model and the values that control it.
If you deploy with the [Rulebricks CLI](/private-deployment/cli), these values are generated from the chart's baseline defaults and autoscale with load. Tune them by hand (or pick larger node instance types) when you need more sustained throughput.
## The Sizing Model
Workers are single-threaded processes doing CPU-bound rule evaluation. Three ideas hold the model together:
1. **Partitions are the concurrency ceiling.** The `solution` Kafka topic's partition count (`rulebricks.hps.workers.solutionPartitions`) is the maximum number of workers that can consume in parallel. It is a ceiling, not a worker quota: idle partitions are effectively free, so the defaults carry roughly 2x headroom over the maximum worker count, because partition counts can be raised but never lowered.
2. **Workers scale out, not up.** Each worker can use up to one full CPU core. The default request is lower so a warm fleet packs efficiently, and bursts add replicas for throughput.
3. **KEDA scales on backlog.** Workers run as a Deployment scaled by KEDA on Kafka consumer lag, so scale-out creates pods in parallel and delivers burst capacity in seconds.
Two rules must always hold:
- `keda.maxReplicaCount` must stay at or below `solutionPartitions`; a worker beyond the partition count would sit idle.
- `solutionPartitions` must match the partition count of the actual `solution` topic, whether reconciled by the chart from `kafka.topics` or pre-created on an [external Kafka cluster](/private-deployment/external-services#topics-to-pre-create).
The CLI validates both rules before anything reaches your cluster.
## Worker Values
| Parameter | Type | Default | Description |
| :------------------------------------------ | :------ | :------------------------------------- | :---------------------------------------------------- |
| `rulebricks.hps.replicas` | integer | `3` | HPS gateway replicas |
| `rulebricks.hps.workers.replicas` | integer | `8` | Base worker replica count |
| `rulebricks.hps.workers.solutionPartitions` | integer | `128` | Partition count of the `solution` topic (the ceiling) |
| `rulebricks.hps.workers.resources.*` | object | 250m request / 1 CPU limit, 1Gi memory | Per-worker resources; scale out for throughput |
## KEDA Autoscaling
| Parameter | Type | Default | Description |
| :-------------------------------------------- | :------ | :------ | :----------------------------- |
| `rulebricks.hps.workers.keda.enabled` | boolean | `true` | Enable KEDA autoscaling |
| `rulebricks.hps.workers.keda.minReplicaCount` | integer | `8` | Minimum workers |
| `rulebricks.hps.workers.keda.maxReplicaCount` | integer | `64` | Maximum workers |
| `rulebricks.hps.workers.keda.pollingInterval` | integer | `5` | Seconds between metric checks |
| `rulebricks.hps.workers.keda.cooldownPeriod` | integer | `300` | Seconds before scale-down |
| `rulebricks.hps.workers.keda.lagThreshold` | integer | `50` | Kafka lag threshold (messages) |
| `rulebricks.hps.workers.keda.cpuThreshold` | integer | `25` | CPU percentage backup trigger |
Lag is measured in messages. HPS splits bulk requests into bounded chunks, so each message represents roughly 50 to 150ms of work, and the default threshold of 50 biases toward early scale-out during bursts.
```mermaid
flowchart LR
A[Kafka Consumer Lag] --> B{lag > threshold?}
B -->|Yes| C[Scale Up Workers]
B -->|No| D{CPU > threshold?}
D -->|Yes| C
D -->|No| E[Maintain Current]
F[Cooldown Period] --> G{No scale triggers for 5 min?}
G -->|Yes| H[Scale Down]
G -->|No| E
```
## Scaling Beyond the Defaults
```yaml
# Higher-throughput configuration
# (update the kafka.topics partition counts to match solutionPartitions;
# kafka.provisioning only applies to external Kafka)
# Your node-pool should have enough capacity to support the maximum number of workers
rulebricks:
hps:
replicas: 4
workers:
solutionPartitions: 96
keda:
minReplicaCount: 12
maxReplicaCount: 96
```
When raising `solutionPartitions`, the `solution` and `solution-response`
topics must grow with it. In-cluster installs converge automatically on
upgrade via the chart's topic provisioning; on external Kafka you raise the
partition counts yourself.
## Burst Pools, Priorities, and Image Pre-Pull
The chart is designed to keep stateful and ingress infrastructure stable while worker capacity scales elastically:
| Parameter | Default | Description |
| :----------------------------------------- | :------ | :---------------------------------------------------------------------- |
| `global.priorityClasses.enabled` | `true` | Creates release-scoped critical and burst PriorityClasses |
| `rulebricks.hps.imagePrepull.enabled` | `true` | Runs a DaemonSet that pre-pulls HPS server and worker images onto nodes |
| `rulebricks.hps.workers.priorityClassName` | unset | Defaults to the release burst class when priority classes are enabled |
The CLI's cluster-setup templates use a burst node pool labeled and tainted `rulebricks.com/pool=burst`. Worker pods tolerate and prefer that pool, while critical services such as Kafka, ClickHouse, and the database can use the release critical class. If you run a fixed cluster with no autoscaler or dedicated burst pool, consider disabling `global.priorityClasses.enabled` or explicitly setting worker priority to match your platform policy.
## Other Scaling Surfaces
| Component | Scaling Method | Trigger |
| :---------- | :------------- | :-------------- |
| Traefik | HPA | CPU utilization |
| HPS Workers | KEDA | Kafka lag, CPU |
| Vector | Manual | Log volume |
Throughput also depends on how clients call the solve API: bulk payloads amortize network cost, and the [request size limits](/private-deployment/external-services#request-size-limits) describe the byte-first admission model.
To verify a deployment under load, use the [benchmarking toolkit](https://github.com/rulebricks/helm/tree/main/benchmarks) in the helm repository.
## 1. Create your values file
/private-deployment/quick-start
Deploy Rulebricks to your Kubernetes cluster using our official Helm charts.

Our unified Helm chart deploys Rulebricks and all its dependencies to your Kubernetes cluster in a single command. You'll need:
- **Kubernetes 1.29+** (EKS, GKE, AKS, or self-managed; our [cluster templates](/private-deployment/cli#cluster-setup) provision 1.34)
- **Helm 3.2.0+**
- **kubectl** configured for your cluster
- A **domain name** you control
The chart handles all service dependencies, configurations, and migrations
automatically. You provide a values file with your settings, and Helm
coordinates the rest.
Prefer a guided setup? The [Rulebricks CLI](/private-deployment/cli) walks you
through configuration interactively, sizes the deployment from your cluster's
available resources, and generates a valid values file before deploying the
same chart.
### Quick Start
```bash
# 1. Create your values file
# 2. Install the chart
helm install rulebricks oci://ghcr.io/rulebricks/helm/stack \
--namespace rulebricks \
--create-namespace \
-f your-values.yaml
# 3. Follow the on-screen instructions for DNS setup
# 4. Enable TLS once DNS is configured
helm upgrade rulebricks oci://ghcr.io/rulebricks/helm/stack \
--namespace rulebricks \
--reuse-values \
--set global.tlsEnabled=true
```
### Single-Phase Installation (with external-dns)
If you're on AWS, GCP, or Azure and want fully automated DNS:
```bash
helm install rulebricks oci://ghcr.io/rulebricks/helm/stack \
--namespace rulebricks \
--create-namespace \
-f your-values.yaml \
--set external-dns.enabled=true \
--set global.externalDnsEnabled=true \
--set global.tlsEnabled=true
```
See [Example Configurations](#example-configurations) below for minimal values files, and visit the [YAML Reference](/private-deployment/configuration-reference) for a complete breakdown of all available options.
## What am I deploying?
You're deploying a complete Rulebricks stack to your existing Kubernetes cluster. The chart installs multiple integrated services, pre-configured for production workloads.
| Component | Purpose | Enabled by Default |
| :----------------------- | :----------------------------------------------------- | :----------------: |
| **Rulebricks** | Core application and high-performance solver | ✓ |
| **Supabase** | PostgreSQL database and authentication | ✓ |
| **Kafka (Strimzi)** | Message queue for async rule execution and logs | ✓ |
| **Traefik** | Ingress controller with automatic TLS | ✓ |
| **cert-manager** | Let's Encrypt certificate provisioning | ✓ |
| **KEDA** | Event-driven autoscaling for workers | ✓ |
| **Vector** | Decision log aggregation and forwarding | ✓ |
| **ClickHouse** | Decision log querying and ClickStack telemetry storage | ✓ |
| **ClickStack / HyperDX** | Built-in traces, operational logs, and observability UI | ✓ |
| **Prometheus** | Metrics collection (kube-prometheus-stack) | ✓ |
| **external-dns** | Automatic DNS record management | ✗ |
For how these components interact, see [Architecture](/private-deployment/architecture).
### Database Options
**Self-hosted (default):** PostgreSQL runs in your cluster with persistent storage. You have full control and data never leaves your infrastructure.
**Managed Supabase:** Use Supabase Cloud instead. Set `supabase.enabled: false` and provide your project credentials. The chart automatically configures your managed project. We are also able to manage your database for you, should you prefer a hybrid deployment configuration.
## Example Configurations
We recommend consulting the [Changelog](/changelog) and pinning `global.version` to the release containing the latest fixes/features. That single version selects the app, HPS, and HPS worker images together.
### Development / Testing
Minimal configuration for evaluation purposes. Self-hosts Supabase.
```yaml
# values.yaml
global:
domain: ''
email: ''
licenseKey: ''
version: '' # e.g. "1.8.17"
smtp:
# Everything else uses defaults
```
### Production with Supabase Cloud
Automatic DNS, external database, shared object storage.
```yaml
# production-values.yaml
global:
domain: ''
email: ''
licenseKey: ''
version: ''
tlsEnabled: true
externalDnsEnabled: true
smtp:
# Shared object storage for decision logs (and database backups
# when self-hosting Supabase)
storage:
provider: s3 # s3, azure-blob, or gcs
bucket: ''
region: ''
s3:
iamRoleArn: ''
supabase:
url: 'https://abcd1234.supabase.co'
projectRef: 'abcd1234'
anonKey: 'from-supabase-dashboard'
serviceKey: 'from-supabase-dashboard'
# Account > Settings > Access Tokens
accessToken: '${SUPABASE_ACCESS_TOKEN}'
supabase:
enabled: false # Don't deploy self-hosted
# Ensure this is configured properly
external-dns:
enabled: true
provider:
name: route53
```
### Cloud-Specific Values
Each provider needs a compatible StorageClass and external-dns configuration:
```yaml
# AWS EKS (requires EBS CSI driver; use IRSA for AWS integrations)
storageClass:
create: true
provisioner: ebs.csi.aws.com
type: gp3
external-dns:
enabled: true
provider:
name: route53
```
```yaml
# Google GKE (uses Workload Identity; annotate service accounts accordingly)
storageClass:
create: true
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-balanced
external-dns:
enabled: true
provider:
name: google
google:
project: 'your-gcp-project'
```
```yaml
# Azure AKS
storageClass:
create: true
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
external-dns:
enabled: true
provider:
name: azure
azure:
resourceGroup: 'your-resource-group'
subscriptionId: 'your-subscription-id'
```
## Caveats & Limits
### Cluster Requirements
The Helm chart deploys to your **existing** Kubernetes cluster. You're responsible for:
- Cluster provisioning and scaling
- Node pool configuration
- Storage provisioner (e.g., AWS EBS CSI driver)
- Network policies and security
See [example-min-cluster.yaml](https://github.com/rulebricks/helm/blob/main/example-min-cluster.yaml) for minimum EKS cluster specifications, or the CLI's [cluster-setup resources](/private-deployment/cli#cluster-setup) for ready-made AWS, Azure, and GCP starting points.
### Air-Gapped and Private Registry Deployments
Rulebricks images are mirrored under the `rulebricks/*` image path. Set `global.imageRegistry` when your cluster must pull from a private registry mirror, and set the matching per-subchart image keys for upstream charts that do not read the parent global value. The Rulebricks CLI can generate those image overrides for you.
Rulebricks can run nearly air-gapped with these exceptions:
| Feature | External Dependency | Can Disable? |
| :--------------- | :------------------------ | :---------------------------------- |
| Managed Supabase | Supabase Cloud API | Yes, use self-hosted |
| AI Features | OpenAI API | Yes, set `global.ai.enabled: false` |
| Log Forwarding | External sinks (S3, etc.) | Yes, use console sink only |
| TLS Certificates | Let's Encrypt | Yes, bring your own certs |
| Fonts | Google Fonts | Requires customization |
| Analytics | Sentry | Requires customization |
## Installing Updates
Rule engines usually take critical roles, so we don't release updates continuously. We make upgrades easy with zero downtime, but you control when they happen.
The Rulebricks product version is pinned by a single value, `global.version`. It selects the `rulebricks/app`, `rulebricks/hps`, and `rulebricks/hps:worker-*` images together, along with app-backed migration jobs, so there's no per-image tag to keep in sync.
First, visit our [Changelog](/changelog) and find the version containing the fixes/features you need.
Then, edit `global.version` in your `values.yaml` file:
```yaml
global:
version: '1.8.17'
```
Finally, run:
```bash
# Upgrade to the pinned version
helm upgrade rulebricks oci://ghcr.io/rulebricks/helm/stack \
--namespace rulebricks \
-f your-values.yaml
```
The chart version controls Kubernetes packaging and infrastructure templates;
it doesn't select the application image version. Production deployments should
pin both: a chart version via `helm install --version` and a product version
via `global.version`.
If anything goes wrong during install or upgrade, see [Troubleshooting](/private-deployment/troubleshooting).
## Storage & Backups
/private-deployment/storage-and-backups
Configure shared object storage and database backups for your private Rulebricks deployment.
# Storage & Backups
Rulebricks uses one cloud provider, one identity, and one bucket (or container) for all storage-backed features. Decision logs and database backups are key prefixes within that single bucket, so adding more data types later never means another identity or bucket to wire up.
## Shared Object Storage
| Parameter | Type | Default | Description |
| :---------------------------------- | :----- | :---------------- | :--------------------------------------------------- |
| `global.storage.provider` | string | `"s3"` | Storage backend: `s3`, `azure-blob`, or `gcs` |
| `global.storage.bucket` | string | must be set | Bucket name (S3/GCS) or storage account (Azure Blob) |
| `global.storage.region` | string | must be set | Bucket region |
| `global.storage.paths.decisionLogs` | string | `"decision-logs"` | Key prefix for the decision log archive |
| `global.storage.paths.dbBackups` | string | `"db-backups"` | Key prefix for database backups |
Identity is configured per provider. Workload identity is preferred everywhere; static credentials are an escape hatch.
| Provider | Preferred Identity | Fallback |
| :----------- | :------------------------------------------- | :----------------------------------------------- |
| `s3` | `global.storage.s3.iamRoleArn` (IRSA) | `global.storage.s3.existingSecret.name` |
| `azure-blob` | `global.storage.azure.clientId` + `tenantId` | `global.storage.azure.connectionStringSecretRef` |
| `gcs` | `global.storage.gcp.serviceAccountEmail` | n/a |
```yaml
global:
storage:
provider: s3 # s3, azure-blob, or gcs
bucket: my-rulebricks-data
region: us-west-2
s3:
iamRoleArn: arn:aws:iam::123456789012:role/rulebricks-storage
paths:
decisionLogs: decision-logs
dbBackups: db-backups
```
Every component that touches the bucket (Vector, ClickHouse, the backup CronJob) authenticates through the same workload identity pattern, so bucket credentials stay in Kubernetes service account configuration rather than application containers.
**Upgrade note:** older values that split storage into
`global.storage.decisionLogs.*` and `global.storage.dbBackups.*` (per-purpose
buckets and regions) are still honored as overrides, but new installs should
use the unified `bucket`, `region`, and `paths.*` shape shown above.
If you use the [Rulebricks CLI](/private-deployment/cli), the wizard collects this storage backend during `rulebricks init` and generates the values above for you. The CLI's [cluster-setup resources](https://github.com/rulebricks/cli/tree/main/cluster-setup) also create compatible buckets and identity roles per cloud.
What lands in the bucket:
- **Decision logs** under `paths.decisionLogs`: a compressed NDJSON archive written by Vector and queried by ClickHouse. See [Decision Logs](/private-deployment/decision-logs).
- **Database backups** under `paths.dbBackups`: logical dumps of the self-hosted Supabase database, covered below.
## Database Backups
The Supabase PostgreSQL instance holds all application state and should be backed up regularly if self-hosting. The bundled deployment uses a PersistentVolumeClaim, so data survives pod restarts, but PVCs are not a meaningful backup strategy.
Enable `backup.enabled` and the chart schedules logical backups (`pg_dumpall` for roles plus `pg_dump` in custom format, uploaded via `rclone`) to your bucket under `global.storage.paths.dbBackups`:
```yaml
backup:
enabled: true
schedule: '0 2 * * *'
retentionDays: 30
```
| Parameter | Type | Default | Description |
| :------------------------------- | :------ | :----------------- | :---------------------------------------------------------- |
| `backup.enabled` | boolean | `false` | Enable scheduled database backups |
| `backup.schedule` | string | `"0 2 * * *"` | Cron schedule for the backup CronJob |
| `backup.retentionDays` | integer | `30` | Days to retain restorable backup data |
| `backup.superuser` | string | `"supabase_admin"` | Postgres superuser role used for logical dumps and restores |
| `backup.ttlSecondsAfterFinished` | integer | `2592000` | Seconds to retain completed backup Jobs and their pod logs |
| `backup.jobHistory.successful` | integer | `30` | CronJob history limit for successful Jobs |
| `backup.jobHistory.failed` | integer | `30` | CronJob history limit for failed Jobs |
Logical dumps run as `backup.superuser` so they can read schemas owned by `supabase_admin` (auth, storage, realtime, and so on).
Backups are only available for self-hosted Supabase. The default history
limits assume the default daily schedule; increase `backup.jobHistory.*` if
you back up more frequently and need every completed Job visible for the full
TTL window.
### On-Demand Backups and Restores
The [Rulebricks CLI](/private-deployment/cli) manages backups outside the schedule. Both commands require `backup.enabled: true` on the deployment (in addition to self-hosted Supabase and configured object storage):
```bash
# Trigger an on-demand backup
rulebricks backup my-deployment
# List backups in object storage and interactively restore one
rulebricks restore my-deployment
```
The restore command lists available backups from object storage and asks for confirmation before restoring.
### Block-Level Snapshots
As a secondary measure on AWS with EBS-backed PVCs, you can also schedule EBS snapshots of the database volume. This captures a point-in-time copy at the block level without impacting the running database.
## Distributed Tracing
/private-deployment/tracing
Distributed tracing for private Rulebricks deployments - view traces in the bundled ClickStack/HyperDX UI by default, or emit OpenTelemetry traces to Elastic APM, a generic OTLP/HTTP backend, or Azure Monitor.
# Distributed Tracing
Distributed tracing gives you the **full lineage of a request as it moves through every service** - from the ingress, through the app or the High Performance Server (HPS), across the Kafka work queue, into the worker that evaluates your rule, and back. Each hop is a span in a single trace, so you can see exactly where time is spent and where an error occurred.
Tracing is **self-hosted only** and **on by default**: default installs ship [ClickStack](https://clickhouse.com/use-cases/observability) (`global.clickstack.enabled: true`) - an in-cluster OpenTelemetry collector backed by ClickHouse, with the HyperDX UI at `observability.` - and the app, HPS, and workers emit OTLP spans to it out of the box.
If you would rather use your own backend, disable ClickStack and configure `global.tracing.*` instead. Rulebricks then deploys a standalone [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) that forwards traces to a **backend of your choice**. The instrumentation is backend-agnostic - the destination is selected at the collector's export step, so you can switch backends without touching the app. Three BYO destinations are supported (works on both AWS and Azure):
- **`elastic`** (default) - your own (BYO) **Elastic APM** endpoint (Elastic Cloud, the Elastic add-on from a cloud marketplace, or self-hosted). Traces are viewable in Kibana APM.
- **`otlp`** - any **generic OTLP/HTTP** backend (Grafana Tempo, Honeycomb, Jaeger, vendor OTLP gateways, or a cloud provider's OTLP ingestion endpoint).
- **`azure-monitor`** - **Azure Monitor / Application Insights**, using an Application Insights connection string.
Tracing complements, and does not replace, the existing signals: **decision logs** still archive to object storage / ClickHouse, and **Prometheus metrics** still power Grafana. See [Decision Logs](/private-deployment/decision-logs) and [Monitoring](/private-deployment/monitoring).
## Topology
```
Client ─▶ Traefik ─▶ HPS API ─▶ Kafka (solution) ─▶ HPS Worker ─▶ evaluateRule
│ │ │
└─────────┴──────── OTLP spans ────────────┘
│
OpenTelemetry Collector
│
▼
ClickStack / HyperDX (default) | Elastic APM | generic OTLP backend | Azure Monitor
```
- **Traefik** is the root span and propagates the W3C `traceparent` header to backends.
- **App** and **HPS API** continue the trace and propagate context on outgoing calls.
- The **HPS API → worker** hop runs over Kafka. Rulebricks injects the trace context into the Kafka message headers and the worker extracts it, so the worker's rule-evaluation span is a child of the originating request - the cross-service hop that would otherwise break the lineage.
## Enabling tracing (CLI)
With the default ClickStack observability feature enabled, tracing works out of the box and nothing below is required. To use a BYO backend instead, the deployment wizard exposes a **Distributed Tracing** feature: enable it, pick a **destination** (Elastic APM, generic OTLP/HTTP, or Azure Monitor), and provide the endpoint/credentials for that backend. Equivalent config-file fields per destination:
```yaml
# Elastic APM (default)
features:
tracing:
enabled: true
destination: "elastic" # elastic | otlp | azure-monitor
samplingRatio: 1.0 # 0.0 - 1.0; applied at the source SDKs
elastic:
endpoint: "https://.apm..cloud.es.io:443"
authMode: "secret-token" # secret-token | api-key | none
secretToken: ""
```
```yaml
# Generic OTLP/HTTP backend (Grafana Tempo, Honeycomb, Jaeger, ...)
features:
tracing:
enabled: true
destination: "otlp"
otlp:
endpoint: "https://otlp-gateway.example.com/otlp"
authMode: "bearer" # none | bearer | api-key | header
token: "" # bearer/api-key credential
```
```yaml
# Azure Monitor / Application Insights
features:
tracing:
enabled: true
destination: "azure-monitor"
azureMonitor:
connectionString: "InstrumentationKey=...;IngestionEndpoint=https://..."
```
The CLI wires the collector, the app/HPS/worker OTLP endpoints, and Traefik tracing automatically, regardless of destination.
## Enabling tracing (Helm)
With the default ClickStack install there is nothing to enable - the ClickStack gateway owns the `-otel-collector` service and traces land in ClickHouse/HyperDX automatically.
For a BYO backend, disable ClickStack (`global.clickstack.enabled: false`) and configure `global.tracing` so the application subcharts can read it; the standalone collector only deploys when ClickStack is off. The `destination` key selects which backend block applies:
| Parameter | Type | Default | Description |
| :---------------------------------------------- | :------ | :------------- | :---------------------------------------------------------- |
| `global.tracing.enabled` | boolean | `false` | Deploy the collector and instrument workloads |
| `global.tracing.destination` | string | `elastic` | `elastic` \| `otlp` \| `azure-monitor` |
| `global.tracing.samplingRatio` | number | `1.0` | Head-sampling ratio applied by the app SDKs |
| `global.tracing.collector.replicas` | int | `1` | OpenTelemetry Collector replicas |
| `global.tracing.elastic.endpoint` | string | `""` | OTLP endpoint of your Elastic APM (`destination: elastic`) |
| `global.tracing.elastic.authMode` | string | `secret-token` | `secret-token` \| `api-key` \| `none` |
| `global.tracing.elastic.secretToken` / `apiKey` | string | `""` | Inline credential (or use `elastic.existingSecret`) |
| `global.tracing.otlp.endpoint` | string | `""` | OTLP/HTTP endpoint of the backend (`destination: otlp`) |
| `global.tracing.otlp.authMode` | string | `none` | `none` \| `bearer` \| `api-key` \| `header` |
| `global.tracing.otlp.token` / `apiKey` / `headerValue` | string | `""` | Inline credential (or use `otlp.existingSecret`) |
| `global.tracing.otlp.headers` | object | `{}` | Extra static (non-secret) export headers |
| `global.tracing.azureMonitor.connectionString` | string | `""` | App Insights connection string (`destination: azure-monitor`) |
```yaml
# Elastic APM (default destination)
global:
tracing:
enabled: true
destination: "elastic"
elastic:
endpoint: "https://.apm..cloud.es.io:443"
authMode: "secret-token"
# Recommended for production: reference an existing Secret.
existingSecret:
name: "elastic-apm"
secretTokenKey: "secret-token"
```
```yaml
# Generic OTLP backend
global:
tracing:
enabled: true
destination: "otlp"
otlp:
endpoint: "https://otlp-gateway.example.com/otlp"
authMode: "bearer"
existingSecret:
name: "otlp-creds"
tokenKey: "token"
```
```yaml
# Azure Monitor
global:
tracing:
enabled: true
destination: "azure-monitor"
azureMonitor:
existingSecret:
name: "app-insights"
connectionStringKey: "connection-string"
```
The collector service is `http://-otel-collector..svc.cluster.local:4318` (HTTP) / `:4317` (gRPC).
When `global.tracing.enabled` is `true`, the endpoint (or connection string) for the selected `destination` is required - `elastic.endpoint`, `otlp.endpoint`, or `azureMonitor.connectionString`. The chart's schema and the CLI both reject an enabled-but-unconfigured tracing block before install.
## Application & container logs
Tracing is most useful when logs **join** traces. Enable application/container log shipping to send pod logs to a customer-managed backend via a Vector agent (a DaemonSet, separate from the decision-log pipeline). Supported destinations are Elasticsearch/Kibana, Grafana Loki, and generic HTTP. The agent parses structured JSON log lines and lifts `trace_id` / `span_id` so your log backend can link a log to its trace.
On AKS and EKS, prefer the cloud-native container log collector when you want
logs in Azure Monitor / Log Analytics or Amazon CloudWatch Logs. Rulebricks
emits structured JSON with `trace_id` / `span_id`, so Azure Container Insights
and the Amazon CloudWatch Observability EKS add-on can collect and correlate
pod stdout without Rulebricks deploying a cloud-specific log shipper.
```yaml
features:
logging:
appLogs:
enabled: true
destination: "elasticsearch"
elasticsearch:
endpoint: "https://.es..cloud.es.io:9243"
index: "rulebricks-app-logs"
authMode: "basic" # basic | api-key | none
username: "elastic"
password: ""
```
```yaml
features:
logging:
appLogs:
enabled: true
destination: "loki"
loki:
endpoint: "https:///loki/api/v1/push"
```
```yaml
features:
logging:
appLogs:
enabled: true
destination: "generic"
generic:
endpoint: "https://logs.example.com/ingest"
authHeader: "Bearer "
```
## BYO log sink validation
The Rulebricks Vector agent uses the same collection and transform pipeline for all BYO sinks:
- It tails Kubernetes pod stdout/stderr via `kubernetes_logs`.
- It excludes the decision-log Vector pods to avoid double-shipping decision logs.
- It parses JSON service log lines and lifts `trace_id` / `span_id`; non-JSON lines still pass through.
- It then writes the normalized event to exactly one sink: Elasticsearch, Loki, or generic HTTP.
The generated Vector configs are validated by the CLI/chart verification suite, so the pipeline is structurally sound before deployment. Runtime reliability depends on the destination accepting the generated payload and credentials:
- **Elasticsearch/Kibana**: uses Vector's `elasticsearch` sink in bulk mode, with optional basic auth or API key auth and TLS certificate verification.
- **Grafana Loki**: uses Vector's `loki` sink with JSON encoding and default labels (`app`, `namespace`, `pod`, `container`). If your Loki tenant has strict label-cardinality rules, override labels to keep only the labels you want indexed.
- **Generic HTTP**: uses Vector's `http` sink with JSON encoding and an optional `Authorization` header. Use this only for endpoints that accept Vector-style JSON log events; provider-specific signing flows (for example Azure Data Collection Rules or AWS SigV4) are intentionally not handled here.
For Azure Monitor Log Analytics and Amazon CloudWatch Logs, prefer the cloud-native collectors below instead of the generic HTTP sink.
**Decision logs stay in ClickHouse.** They are not double-written to Elasticsearch. The agent explicitly excludes the Vector pods (`app.kubernetes.io/name notin (vector,vector-agent)`), so the decision logs the aggregator prints to stdout are never shipped to Elastic. To correlate decision logs with traces, query the `trace_id` / `span_id` columns now present on decision-log records.
## Cloud-native log collection
For cloud-managed Kubernetes logs, let the cloud provider own the log agent:
- **Azure AKS**: enable Container Insights / Azure Monitor Agent so stdout and stderr land in Log Analytics (`ContainerLogV2`). Pair it with Azure Managed Prometheus + Azure Managed Grafana for metrics and Application Insights for traces.
- **AWS EKS**: enable the Amazon CloudWatch Observability EKS add-on with container logs / OTel Container Insights. It collects application pod logs into CloudWatch Logs and can be managed outside the Rulebricks chart.
This keeps the Rulebricks chart cloud-neutral while still giving operators a complete logs + traces story. Search for the `X-Rulebricks-Trace-Id` response header value in your trace backend, then search the cloud log table or log group for the same `trace_id`.
## Viewing lineage
How you view traces depends on the destination: **Kibana APM** for `elastic`, your backend's UI (Grafana Tempo, Honeycomb, Jaeger, etc.) for `otlp`, or **Application Insights / Azure Monitor** for `azure-monitor`. Either way the spans are emitted identically, so the same `rulebricks-app` → `rulebricks-hps` → `rulebricks-hps-worker` waterfall appears. The steps below use Kibana as the example.
1. Copy the `X-Rulebricks-Trace-Id` response header from any API response or error. HPS and self-hosted app APIs also echo a W3C `traceparent` header.
2. Paste that trace id into **Kibana APM**, **Grafana Tempo**, **Jaeger**, or your OTLP backend's trace search.
3. Open the transaction (for example `POST /api/v1/solve/:slug`) to see the **waterfall**: Traefik → HPS API → Kafka produce → worker `solve` → rule evaluation.
4. Use the logs view on that trace to see app/HPS/worker log lines that share the trace's `trace_id`.
5. Decision logs (in ClickHouse) carry the same `trace_id` / `span_id`, so you can pivot from a trace to its decision record.
## Verifying the collector
```bash
kubectl get deploy -n rulebricks -l app.kubernetes.io/name=otel-collector
kubectl logs -n rulebricks deploy/-otel-collector
```
If traces are not arriving in Elastic, check the collector logs for export errors (endpoint reachability, auth) and confirm `OTEL_EXPORTER_OTLP_ENDPOINT` is set on the app/HPS pods:
```bash
kubectl set env deploy/-app --list -n rulebricks | grep OTEL
```
## Troubleshooting
/private-deployment/troubleshooting
Diagnose and fix common issues in private Rulebricks deployments, from failed migrations to TLS and autoscaling problems.
# Troubleshooting
Common diagnostics for private deployments, roughly in the order problems tend to appear.
## Migration Jobs
The chart runs a migration job on every install and upgrade. Failures here are usually inaccurate values or, for managed Supabase, a project that doesn't exist yet.
```bash
# Check job status
kubectl get jobs -n rulebricks
# Get detailed error
kubectl describe job rulebricks-db-migrate-1 -n rulebricks
# Check pod logs (self-hosted Supabase)
kubectl logs job/rulebricks-db-migrate-1 -n rulebricks --all-containers
# Check pod logs (managed Supabase)
kubectl logs job/rulebricks-managed-supabase-setup-1 -n rulebricks
```
**Common causes:**
- Database not ready (increase readiness wait)
- Invalid credentials
- Network policy blocking access
- Supabase Cloud project not created yet, or wrong access token
To inspect which migrations have applied on a self-hosted database:
```bash
kubectl exec -it deploy/rulebricks-supabase-db -n rulebricks -- \
psql -U postgres -c "SELECT * FROM schema_migrations ORDER BY applied_at;"
```
## TLS Certificates
```bash
# Check cert-manager logs
kubectl logs -n cert-manager -l app=cert-manager
# Check certificate status
kubectl get certificates -n rulebricks
kubectl describe certificate rulebricks-tls -n rulebricks
# Check ClusterIssuer
kubectl describe clusterissuer rulebricks-letsencrypt
```
The most common cause is enabling `global.tlsEnabled` before DNS records resolve to the cluster's load balancer. Let's Encrypt must be able to reach your domain.
## Workers Not Scaling
```bash
# Check KEDA
kubectl get scaledobject -n rulebricks
kubectl describe scaledobject rulebricks-hps-worker-scaler -n rulebricks
# Check Kafka consumer lag
kubectl exec -it rulebricks-kafka-combined-0 -n rulebricks -- \
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group generic-workers
```
**Common causes:**
- Kafka consumer group not found
- KEDA unable to reach Kafka
- Incorrect threshold configuration
For the sizing model behind worker scaling (and why partition counts matter), see [Performance & Scaling](/private-deployment/performance).
## External Kafka or Redis Problems
If you've pointed the chart at your own Kafka or Redis and solves are failing or timing out, work through the [verification checklist](/private-deployment/external-services#verification-checklist), which covers the HPS health endpoints, an end-to-end smoke test, and a table of common failure modes.
## Log Collection
```bash
# All pods in namespace
kubectl logs -n rulebricks -l app.kubernetes.io/instance=rulebricks --all-containers
# Specific component with follow
kubectl logs -n rulebricks -l app.kubernetes.io/component=hps-worker -f
```
## Clean Reinstall
As a last resort, fully remove the deployment and start over:
```bash
# Full cleanup including data
helm uninstall rulebricks -n rulebricks
kubectl delete pvc --all -n rulebricks
kubectl delete namespace rulebricks
# Reinstall
helm install rulebricks oci://ghcr.io/rulebricks/helm/stack \
--namespace rulebricks \
--create-namespace \
-f your-values.yaml
```
Deleting PVCs destroys the self-hosted database and all application state.
Make sure you have a [backup](/private-deployment/storage-and-backups) before
a clean reinstall on anything but a fresh evaluation.
## Still Stuck?
Email [support@rulebricks.com](mailto:support@rulebricks.com) with the failing component's logs, or open an issue on the [helm](https://github.com/rulebricks/helm/issues) or [CLI](https://github.com/rulebricks/cli/issues) repository.
## Releases in Rulebricks
/releases
Structured version-controlled deployments for your Rulebricks rules and flows. Learn how releases separate development from production deployment.
# Releases in Rulebricks
Building business logic is only half the story. The rules and flows you create in Rulebricks represent critical decision-making processes—pricing calculations, eligibility checks, fraud detection, compliance validation—that drive real outcomes in your applications. But between the moment you finish building a rule and the moment it starts serving production traffic, there exists a gap that many teams overlook until it becomes a problem.
Releases in Rulebricks enable structured, version-controlled deployments for your rules and flows.
Rather than having changes take effect the moment you click "publish," Releases creates a deliberate separation between ongoing development work and production deployments.
## The Releases Tab
When you navigate to the Releases tab in your Rulebricks workspace, you encounter a unified view of your deployment landscape. The interface organizes your assets—both rules and flows—into two distinct categories that reflect their current deployment status.

At the top of the screen, you can toggle between viewing rules and flows, and filter by environment using the dropdown selector. This environment-centric view is intentional: rather than asking "what state is this rule in?" you ask "what is deployed to staging?" or "what is live in production?" This subtle shift in perspective mirrors how operations teams actually think about deployments.
The interface divides releases into two sections. The Pending section shows releases that have been requested but not yet approved—these represent changes that are in flight, awaiting review before they go live. Below that, the Releases section displays assets that have successfully been released to the selected environment, showing you exactly what version is currently active and when it was deployed.
## Creating and Managing Releases
/releases/creating-and-managing-releases
Learn how to create, approve, and promote releases in Rulebricks. Manage the complete release lifecycle from staging to production.
# Creating and Managing Releases
With environments configured, you can begin the actual work of releasing your business logic. A release in Rulebricks represents a specific version of a rule or flow that has been tagged for deployment to a particular environment. Creating a release does not immediately deploy anything—instead, it opens a request that must be approved before the release becomes active. This two-step process separates the intent to deploy from the actual deployment, creating space for review and validation.
## Starting a New Release
### Open the new release modal
Click the "New Release" button in the Releases tab. The modal that appears guides you through the essential decisions.

### Select your target environment
The environment selection determines where this release will go and, consequently, who needs to approve it. Only environments that allow direct releases appear in this dropdown—if an environment is configured for "promotion only," you cannot create releases directly into it and must instead promote from a lower-level environment.
### Choose your asset and version
The asset selection shows only published rules and flows. This constraint exists because releases target stable, versioned snapshots of your business logic, not work in progress. If the rule or flow you want to release does not appear in the dropdown, return to the Rules or Flows tab and publish it first.
The version selection lets you choose which specific published version to release. This is particularly important when you have made multiple iterations on a rule and want to release a specific checkpoint rather than the latest version.
### Add release notes
The notes field provides an opportunity to document why this release is being created, what changes it contains, or what the approvers should pay attention to during review. These notes become part of the release's permanent record and appear in the activity feed.
## Navigating Releases by Environment
The Releases tab organizes everything by environment because that is how most teams think about deployments. When you select an environment from the dropdown, the view updates to show only releases relevant to that specific deployment target.

The pending section shows releases that have been created but not yet approved. Each pending release displays key information: the asset name, the version number, when the release was opened, how many approvals are still needed, and how many comments have been added. This at-a-glance view helps you quickly identify which releases need attention and their current status in the approval workflow.
Below the pending releases, the released section shows assets that have been successfully deployed to this environment. Each entry indicates the version that is currently active and confirms that the release is live. This serves as a source of truth for what is actually running in each environment.
## The Approval Workflow
When you open a pending release, you enter the detailed view where the actual approval workflow takes place. This screen presents all the information approvers need to make an informed decision, along with tools for collaboration and action.

The header area displays the current approval status—"Pending Approval" for releases still awaiting sign-off, or "Released" for approved deployments. Action buttons allow approvers to approve the release, update the version target if needed, or cancel the release entirely if circumstances have changed.
The approval status panel shows who has approved the release and who is still pending. The scheme indicator reminds everyone whether a single approval is sufficient or if all designated approvers must sign off. As approvals come in, the panel updates in real time, providing clear visibility into how close the release is to going live.
The release asset section shows exactly what is being released—the rule or flow card displays the same information you would see in the Rules or Flows tab, making it easy to review what this release contains without navigating away. You can click through to view the full rule details if needed.
The activity feed on the right captures the complete history of the release: when it was created, who commented, what notes were added, and when approvals were given. Comments support rich formatting and mentions, allowing you to tag specific team members for input or flag concerns that need addressing before approval.
## Promoting Releases
Once a release has been approved in one environment, you may want to advance it to the next stage of your deployment pipeline. Rather than creating a new release from scratch, you can promote the existing release to a higher-level environment. This action preserves the connection between the releases, making it clear that what is running in production came from what was tested in staging.
The promote action appears on released assets and shows only environments at the next level in your hierarchy. Promotion creates a new release in the target environment that references its origin, providing a clear audit trail of how changes flow through your deployment pipeline.
The promoted release still requires approval according to the target environment's configuration, ensuring that each stage maintains its own governance.
## Notifications and Collaboration
/releases/notifications-and-collaboration
Configure release notifications in Rulebricks via email, Slack, Discord, or webhooks. Keep your team informed about pending approvals and deployments.
# Notifications and Collaboration
A release workflow is only as effective as the team's awareness of it. The best-designed approval process fails if approvers do not know releases are waiting for their attention, or if stakeholders learn about deployments only after something goes wrong. Rulebricks addresses this through configurable notifications that keep your team informed at the moments that matter, without overwhelming them with noise.
## Configuring Notifications for Each Environment
Notification settings are configured per environment, recognizing that different deployment stages warrant different levels of attention. A staging environment might need minimal notifications since changes there are routine, while production releases might warrant immediate alerts across multiple channels.
To configure notifications, open the Release Environments modal and locate the notifications column. Each environment shows a bell icon that indicates whether notifications are enabled. Clicking this icon opens the notification configuration for that specific environment.

The notifications button appears alongside other environment properties, making it easy to audit which environments have alerting configured and which do not. This visibility helps ensure that your production environment does not accidentally go unmonitored while less critical environments have elaborate notification setups.
## Choosing What to Notify On
The notification settings screen presents two dimensions of configuration: which events trigger notifications, and which channels receive them. The events tab lets you toggle notifications for each type of release activity, giving you fine-grained control over what your team hears about.

The available events cover the complete release lifecycle:
| Event | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **Release Created** | Fires when someone opens a new release request, alerting approvers that their attention is needed |
| **Release Commented** | Notifies when discussion happens on a release, keeping everyone aware of the conversation |
| **Release Updated** | Triggers when someone changes the version target of an existing release |
| **Release Cancelled** | Notifies when a release is cancelled |
| **Release Approved** | Notifies when an approver gives their approval |
| **Release Fully Approved** | Fires when a release meets all approval requirements and goes live—the moment when API traffic begins serving the new version |
The "Release Fully Approved" event is distinct from individual approvals. In
an "all approvers required" environment, individual approvals might not be
significant on their own, but the final approval that tips the release into
active status is worth calling out.
The **direct mentions** setting deserves special attention. When enabled, team members who are mentioned in release comments receive email notifications about that specific mention. This creates a lightweight way to pull someone into a discussion without adding them as a formal approver or sending notifications about every release event.
## Notification Channels
The channels tab determines how notifications reach your team. Rulebricks supports multiple delivery mechanisms, and you can enable any combination that fits your workflow.
**Email notifications** go directly to specified addresses. You can add individual recipients or quickly add all environment approvers with a single click. Email works well for ensuring notifications reach people even when they are not monitoring real-time communication tools.
**Slack and Discord integrations** allow notifications to flow into your team's existing communication channels. By providing a webhook URL for your Slack or Discord workspace, release events can post messages directly to a dedicated channel. This keeps release activity visible alongside other team discussions and makes it easy to react quickly without context-switching to the Rulebricks interface.
**Custom webhooks** provide maximum flexibility for teams with existing notification infrastructure or specialized requirements. Any URL that can receive a POST request with release event data can be configured as a notification destination, enabling integration with paging systems, custom dashboards, or internal tools.
The power of channel configuration lies in combination. A production
environment might send emails to the on-call engineer, post to a dedicated
Slack channel for visibility, and trigger a webhook that logs the event to
your observability platform. The same release event can fan out to multiple
destinations, ensuring that the right people get informed through the channels
they actually monitor.
## Release URLs and API Access
/releases/release-urls-and-api-access
Understand the difference between Preview URLs and Release URLs in Rulebricks API. Learn integration patterns for production and staging environments.
# Release URLs and API Access
The practical integration of releases into your applications comes down to URLs. When you call a rule or flow via the Rulebricks API, the URL you use determines which version of the logic gets executed. Understanding the distinction between preview URLs and release URLs is essential for building robust applications that take full advantage of the release system.
## Two Ways to Access Your Rules
Every rule and flow in Rulebricks can be accessed through the API using two different URL patterns. The first pattern uses a version number at the end of the URL, directly targeting a specific published version. The second pattern uses an environment slug, which dynamically resolves to whatever version is currently released in that environment.
When you view a pending release, both URL patterns are displayed together, making the distinction clear.

The **Preview URL** follows the pattern `/api/v1/solve/{rule-slug}/{version-number}`. This URL always returns results from the exact version you specify, regardless of what has been released. It provides a stable, predictable endpoint that never changes behavior unless you explicitly change the version number in the URL. Use this URL for testing specific versions or when you need guaranteed consistency.
The **Release URL** follows the pattern `/api/v1/solve/{rule-slug}/{environment-slug}`. This URL resolves to whatever version is currently active in the specified environment. When you approve a new release, the Release URL automatically starts serving the newly released version without any changes to your application code. This dynamic resolution is what makes releases powerful—your production systems can point to the release URL, and deployments happen by approving releases rather than updating configurations.
## Before and After Approval
The relationship between these URLs and the approval workflow is where releases provide their real value. Before a release is approved, the Release URL still points to the previous released version—or returns an error if nothing has ever been released to that environment. The pending release exists in a preparatory state, visible in the Rulebricks interface but not yet affecting API traffic.
Notice the amber warning message beneath the Release URL in a pending release:
"This endpoint will be active/updated after the release is approved." This
indicator makes clear that creating a release does not immediately change what
your API consumers receive.
Once the release is approved, the situation changes. The Release URL now resolves to the newly approved version, and the status indicator turns green.

The green confirmation message—"This endpoint is active and can be used in production"—signals that this version is now live. Any API call to the Release URL will execute this version of the rule. The transition from pending to released happens atomically when the final required approval is given, ensuring there is no ambiguous intermediate state.
## Practical Integration Patterns
Though quite simple, understanding these URL patterns genuinely enables several familiar integration strategies:
**Production systems with controlled updates:** Configure your application to use the Release URL. Your code always calls the same endpoint, and updates happen through the approval workflow rather than code deployments. This separation means you can update business logic without touching your application infrastructure.
**Staging and testing environments:** Use the Preview URL to pin to a specific version while you validate behavior. This ensures your tests run against known logic even if someone approves a new release while your test suite is executing.
**Multi-environment applications:** Some teams use different Release URLs for different parts of their application. A customer-facing pricing service might call the production Release URL, while an internal analytics pipeline calls the staging Release URL. Both are calling the same rule, but they receive different versions based on what has been released to each environment.
**Emergency rollback:** The version-specific Preview URL also serves as a safety mechanism. If something goes wrong with a newly released version, you can temporarily point critical systems to the Preview URL of the previous version while you investigate. This provides an immediate escape hatch that does not require rushing through a new release approval.
Whichever pattern you choose, the key insight is that Rulebricks gives you
control over the relationship between your API calls and the particular
logic/version of decision assets that get executed. Releases are merely a
mechanism for managing that relationship in a deliberate, auditable way.
## Setting Up Environments
/releases/setting-up-environments
Configure release environments in Rulebricks to define deployment targets, approval workflows, and promotion hierarchies for your rules and flows.
# Setting Up Environments
Before you can begin releasing rules and flows, you need to define the environments through which your changes will travel. An environment in Rulebricks represents a deployment target—a conceptual space like "staging" or "production" where a particular version of your business logic will run. The configuration of these environments determines not just where releases can go, but how they get there and who has the authority to approve them.
The design of your environment structure should reflect your organization's actual deployment practices. A small team might operate with a simple two-environment setup: a staging environment for testing and a production environment for live traffic. Larger organizations often require more nuanced hierarchies, perhaps with multiple staging environments for different testing purposes, or regional production environments that receive releases at different times.
## Creating Your First Environment
To configure environments, click the "Release Environments" button in the Releases tab. This opens a modal where you can view existing environments and create new ones. The environment list displays key information at a glance: the environment name, its level in the promotion hierarchy, the number of designated approvers, the approval scheme, and whether notifications are configured.

When creating or editing an environment, you define several critical properties. The environment name serves as its identifier throughout the system—choose something meaningful like "Staging" or "Production EU" rather than cryptic abbreviations. The description field allows you to document the environment's purpose and its place in your release workflow, which proves invaluable when onboarding new team members or auditing your processes months later.
## Understanding Environment Levels
The level setting deserves particular attention because it governs the promotion path for your releases. Environments with lower level numbers are considered earlier in the release pipeline, and releases can only be promoted from a lower level to the next higher level. This constraint is intentional: it prevents releases from skipping stages in your workflow, ensuring that every change passes through the appropriate validation steps before reaching production.

Consider a typical three-environment setup. Your staging environment might be level 1, a pre-production environment level 2, and production level 3. A release created in staging cannot jump directly to production—it must first be promoted to pre-production, approved there, and only then can it advance to production. This sequential progression creates natural checkpoints where your team can validate changes before they move closer to live traffic.
You can also have multiple environments at the same level. This is useful when you have parallel deployment targets, such as regional production environments or multiple staging environments for different feature teams. Releases from a lower-level environment can be promoted to any environment at the next level, giving you flexibility while maintaining the overall progression structure.
## Configuring Approvers and Approval Schemes
Each environment requires at least one approver—a team member authorized to approve releases into that environment. The approvers you designate should align with who has responsibility for changes reaching that stage of deployment. Your staging environment might allow any developer to approve releases, while production might require sign-off from a senior engineer or technical lead.
The approval scheme determines how many approvers must give their consent before a release goes live. With the "Any approver" scheme, a single approval from any designated approver is sufficient. The "All approvers" scheme requires every designated approver to provide explicit approval. Choose based on the risk profile of each environment: staging might use "any approver" for agility, while production might require consensus from all approvers for safety.
The approval scheme and the release creation settings are locked after an environment is created. This design prevents accidental or untracked changes to your approval criteria after releases have begun flowing through the system. If you need to change these settings, you would need to create a new environment with the desired configuration. Plan your environment structure thoughtfully before committing to it.
## Service Level Agreement
/security/service-level-agreement
Rulebricks offers a common SLA with varying minimum response and resolution times for two levels of issues– low, and high severity. Learn more about this agreement here.
## Basic SLA
Rulebricks offers a common SLA with varying minimum response and resolution times for two levels of issues: low, and high severity.
We define "high severity" issues as bugs/errors that prevent the development and correct execution of rule or flow assets on our platform, directly impeding your team from gaining value from our software.
Please note the response and resolution times provided below are maximums, and
you can often expect faster progress, particularly during business hours
within **Pacific Standard Time**.
#### **Low Severity**
- 48 hour maximum response time
- 10 day resolution time
#### **High Severity**
- 24 hour maximum response time
- 3 day resolution time
If Rulebricks breaks the SLA in two consecutive months or over three months in any 12-month period, then Customer may, as its only remedy, terminate this Order Form upon notice and receive a prorated refund of prepaid fees for the remainder of the Subscription Period.
## High SLA
Our High SLA is available to customers of our Embedded (Enterprise) plans and offers response & resolution times at the extremes of what we are able to support, including 24/7 direct on-call lines, 4-8 hour response times, and significant service credits for violations.
Rule engines require a great degree of trust & vendor responsibility. Our High SLA is our word that- should anything go wrong– we'll be online.
---
We have a CommonPaper (https://commonpaper.com/) Cloud Services Agreement template for Rulebricks containing what you see on this page you may request access to for closer review should you wish to move forward.
## System Architecture
/security/system-architecture
Rulebricks' system architecture is built on JS, Postgres, and Redis. Our cloud infrastructure is hosted on Vercel, Supabase, and Upstash. Learn more about our technology stack here.
## Technology Overview
Rulebricks is built using Next JS, Postgres, and Redis, **all of which can be run on your own infrastructure via Kubernetes** if you choose to deploy Rulebricks privately.
Our cloud infrastructure is respectively hosted on Vercel, Supabase, and Upstash. You can find more information about our subprocessors in our [Trust Portal](/security/trust-portal), including the technology they provide, and the regions we operate over them in.
All cloud data is backed up every few hours and retained for 30 days.
For our AI features, we use OpenAI. We do not store any data with OpenAI, and only send data to them when you explicitly take an AI action in the Rulebricks interface.
### Private Hosting
All private deployments are HTTPS and require certification, _provided DNS records are updated immediately after deployment._
We support Kubernetes-based deployment on all major cloud providers, with proven deployments on AWS and Azure.
All private deployments use Helm charts to spin up and run occasional upgrades against the requisite services. We publish upgrades to our application quite often (2-3 times a day), and you can choose to upgrade your private deployment at your convenience, or opt into notifications about major upgrades we recommend you apply.
Most third party services are disabled or re-configured in private deployments, particularly certain logging, user analytics, and AI features.
## Terms Of Service
/security/terms-of-service
Rulebricks' Terms of Service, including our Privacy Policy and other legal information.
## Legal Information
You can find our Terms of Service on our website [here](https://www.rulebricks.com/legal/terms-of-service), along with our Privacy Policy [here](https://www.rulebricks.com/legal/privacy-policy).
Signing up for Rulebricks means you agree to these terms, so do read them before you get started.
## Trust Portal
/security/trust-portal
Access Rulebricks' cybersecurity documentation, SOC 2 reports, and penetration testing results, and review our subprocessors.
# Trust Portal
We maintain all of our cybersecurity documentation, SOC 2 reports, and penetration testing results in our trust portal at [trust.rulebricks.com](https://trust.rulebricks.com). Most documents are available there on request after a short approval step.
If you [schedule a demo](https://calendly.com/prefix-software/rulebricks) with us or [reach out](mailto:support@rulebricks.com), we can expedite your approval process toward our documentation.
## Subprocessors
Rulebricks uses the following third-party services to provide its core functionality:
| Subprocessor | Technology | Description | Website |
| ------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| Vercel (AWS) | Hosting & Edge Caching | Provides cloud hosting and edge caching services. Vercel is used for hosting the Next.js application, which includes React and Node.js environments. They also manage the Edge Cache to enhance content delivery speeds. | [Vercel](https://vercel.com) |
| Supabase | Cloud Database | Acts as a backend service. Supabase offers a PostgreSQL database service, authentication, real-time subscriptions, and storage capabilities. It is used to manage and store all the application data in the cloud securely. | [Supabase](https://supabase.com) |
| Upstash | Redis Cache | Provides managed Redis services. Upstash is used for caching frequently accessed data to reduce latency and improve the performance of database operations. | [Upstash](https://upstash.com) |
### Technology Details
- **Next.js**: A React framework that enables server-side rendering and static site generation, which is hosted on Vercel. _Region – Vercel – IAD1 (Washington, D.C.)_
- **Node 26**: Handles all server-side logic.
- **Vercel Edge Cache**: Utilizes Vercel's global network to cache and serve static and dynamic content at the edge, closest to the users, for faster performance.
- **Supabase (Cloud)**: A cloud-hosted backend-as-a-service that provides not only database solutions but also handles authentication and real-time data functionality. _Region – Supabase – US East (N. Virginia)_
- **Redis (Upstash)**: Rulebricks uses Upstash to manage user rate limits, and temporarily store compressed asset data for increased API performance.
## Video Introduction
/video-introduction
A brief video overview of Rulebricks to get you started building and using rules right away.
### A Brief Overview
There're a lot of pages here. Don't want to read through all of them? Here's a short video to give you a bird's eye view of our platform to get you started working with Rulebricks right away.
You can also find this video at any time within Rulebricks by clicking the
Question Mark icon in the top right corner of the rule editor.
## Rule Warnings
/warnings
Rulebricks automatically analyzes your decision tables and warns you about potential issues before they cause problems in production.
# Rule Warnings
Rulebricks analyzes your decision tables and warns you about structural issues—rows that can never be reached, conditions that shadow later rows, gaps where inputs might fall through.
Warning detection is fairly powerful and can identify situations where conditions are structured in ways that overlap in strange and ambiguous ways, encouraging rule developers to write clearer rules.

Warnings appear in the rule editor footer. Click to see details and jump to the affected rows.
Warnings use **static analysis**—they examine your rule structure without
needing production data. For runtime analysis based on actual traffic, use
[Analysis Tools](/analysis-tools).
## Severity Levels
**Error (Red)** — Definite problems that will cause incorrect behavior. Unreachable rows, catch-alls blocking everything below them. Fix these before publishing.
**Warning (Yellow)** — Likely issues that deserve attention. Partial overlaps, coverage gaps, unhandled boundary values. Review to ensure the behavior is intentional.
**Info (Blue)** — Informational notices. Missing catch-all row, incomplete coverage suggestions. Consider whether these apply to your use case.
## Common Issues
**Catch-All Blocking:** A row with "any" for all conditions matches everything, blocking all rows below it. Move catch-alls to the bottom.
**Unreachable Rows:** Every input that would match a row is already caught by an earlier row. Reorder so more specific conditions come first.
**Coverage Gaps:** A range of values won't match any row. Add handling for the missing range, or verify your catch-all covers it.
See **[Warning Types](/warnings/warning-types)** for detailed explanations and fixes for each warning.
## Warning Types
/warnings/warning-types
Detailed explanations of each warning type in Rulebricks, including what causes them and how to fix them.
# Warning Types
## Errors
### Catch-All Blocking
**Message:** "Row X is a catch-all. Rows Y, Z will never be reached."
A row with "any" for all conditions matches everything, blocking all rows below it.
```
Row 1: If tier = "gold" → Discount 20%
Row 2: If any → Discount 0% ← Catch-all
Row 3: If tier = "platinum" → Discount 30% ← Never reached!
```
**Fix:** Move the catch-all to the bottom.
```
Row 1: If tier = "gold" → Discount 20%
Row 2: If tier = "platinum" → Discount 30%
Row 3: If any → Discount 0% ← Now at the bottom
```
### Unreachable Row
**Message:** "Row X is unreachable because it is fully covered by Row Y."
Every input that would match Row X is already caught by an earlier row.
```
Row 1: If score >= 700 → Approved
Row 2: If score >= 750 → Premium ← Unreachable!
```
Any score >= 750 also satisfies >= 700, so Row 1 catches it first.
**Fix:** Reorder so the more specific condition comes first:
```
Row 1: If score >= 750 → Premium
Row 2: If score >= 700 → Approved
```
## Warnings
### Partial Overlap
**Message:** "Row X partially overlaps with Row Y."
Some inputs match both rows, but not all. The earlier row wins for the overlap.
```
Row 1: If tier = "gold" AND score > 600 → Plan A
Row 2: If score > 700 → Plan B
```
A gold-tier customer with score 750 matches both, but gets Plan A.
**Fix:** If unintentional, make conditions mutually exclusive. If intentional, document why and ignore the warning.
Partial overlaps aren't always bugs—sometimes you want earlier rows to take
precedence.
### Coverage Gap
**Message:** "Column 'X' has a coverage gap: values in [a, b] have no explicit handling."
There's a range of values that won't match any row.
```
Row 1: If age < 18 → Minor rate
Row 2: If age > 65 → Senior rate
← Gap: Ages 18-65 have no explicit handling
```
**Fix:** Add a row for the missing range, or verify your catch-all handles it correctly.
### Boundary Gap
**Message:** "Exact value X = 100 has no explicit handling."
Adjacent conditions leave a specific value unhandled.
```
Row 1: If score < 100 → Fail
Row 2: If score > 100 → Pass
← Gap: Exactly 100 falls through!
```
**Fix:** Use inclusive operators:
```
Row 1: If score < 100 → Fail
Row 2: If score >= 100 → Pass
```
## Info
### Incomplete Coverage
**Message:** "No catch-all row exists. Some inputs may not match any row."
Without a catch-all, unmatched inputs return an error.
**Fix:** Add a final row with "any" for all conditions if you want a default result. Leave it out if you want unhandled cases to error.
This is informational—intentionally omitting a catch-all is valid when you
want unhandled cases flagged as errors.
---
It is important to note that warnings aren't always available. Warnings don't
appear when your rule uses row groups with priorities, individual row
priorities, or OR rows. These features change evaluation semantics in ways
static analysis can't predict—use [Reachability
Analysis](/analysis-tools/reachability-analysis) for runtime validation
instead.
## Assigning Roles
/workspace-management/assigning-roles
Control workspace access with system roles and custom roles with fine-grained permissions.
# Assigning Roles
Roles control what users can access and modify. Assign them from the **Team** tab by clicking the pencil icon next to any user's current role.

## System Roles
**Developer** — Full access to rules, flows, API keys, logs, Dynamic Values, and settings. Cannot edit team membership or billing.
**Editor** — Can view and edit existing rules. Cannot create/delete rules, access API keys, logs, flows, or settings.
**Administrator** — Complete control including team management and billing. Reserved for the workspace owner.
## Custom Roles
For granular control, create custom roles with exactly the permissions you need.
### Create the role
Go to **Team → Roles** and click **Create Custom Role**.

### Configure permissions
Select which capabilities this role should have:

**Rules & Flows** — View, edit, create, delete, publish
**Data & Settings** — API keys, logs, Dynamic Values, settings
**Team** — View members, invite members, manage roles
### Save
Name it descriptively and save.
Custom roles combine with [User
Groups](/workspace-management/creating-tenants) for precise scoping– roles
control what Rulebricks functionality they can use, and user groups control
what data they see.
## User Groups
/workspace-management/creating-tenants
Partition your workspace into isolated User Groups for different teams, clients, or projects.
# User Groups
User Groups partition your workspace into isolated sections. Users only see rules and flows in their assigned groups—useful for multi-team organizations, client isolation, or separating environments.

### Create a group
Go to **Team → User Groups** and click **Create User Group**. Give it a name and description.
### Assign users
Add users to the group. Users can belong to multiple groups. Their [role permissions](/workspace-management/assigning-roles) apply within their group scope.
Administrators & Developers, by default, see all groups and data within. Everyone else sees only their assigned groups, and data explicitly assigned to that group.
User Groups are unlimited on all plans—create as many as you need at no
additional cost.
## Managing Subscriptions
/workspace-management/customer-portal
Manage your Rulebricks subscription, payment information, and billing from the customer portal.
# Managing Subscriptions
### Open account menu
Click **Account** in the top right corner of the dashboard.

### Open billing
Click the **Billing** tab to launch the customer portal (powered by Stripe). From here you can update payment info, view invoices, and change your plan.

If you don't have an active subscription, you'll be redirected to the pricing page instead.
## Inviting Collaborators
/workspace-management/inviting-collaborators
Invite team members to your Rulebricks workspace and manage their access levels.
# Inviting Collaborators
Navigate to the **Team** tab to see your current team members and invite new ones.

### Click Invite
Click the **Invite** button to open the invitation dialog.

### Enter email and select role
Enter their email address and choose a role:
- **Developer** — Full access to rules, flows, API keys, logs, and settings
- **Editor** — Can view and edit existing rules only
### Send invitation
They'll receive an email with instructions to join. Once accepted, they appear in your team list and can immediately access your workspace.
For more granular control, create [custom roles](/workspace-management/assigning-roles) with specific permissions.
Collaborators cannot edit team membership, view billing, or remove the workspace owner—those are admin-only.
## Updating Your Account
/workspace-management/updating-your-account
Update your email, password, and team name from the Account tab.
# Updating Your Account
### Open account menu
Click **Account** in the top right corner of the dashboard.

### Edit your info
Update your email address, password, or team name.
