The Manifest is the entry point for a rule project. It is a single YAML file that ties everything together: it names the project, and lists one or more entries, each of which combines a field schema, an action schema, and a set of rule files.
When working with more than a handful of rule files, it quickly becomes inconvenient to specify every path individually. The manifest provides a single place to describe a complete rule configuration — making it easy to load, validate, and run everything with one reference.
It also supports multiple entries, which lets you manage distinct rule sets (for example, transaction classification rules and fraud detection rules) inside a single project.
name: <project-name>
entries:
- id: <entry-id>
schema: <path-to-field-schema.yaml>
actions: <path-to-action-schema.yaml>
rules:
- <path-to-rule-file.rule>
- <path-to-another-rule-file.rule>
All paths are relative to the manifest file itself. This means you can move the entire project folder without changing any paths.
| Field | Required | Description |
|---|---|---|
name |
optional | A human-readable name for the project |
entries |
✅ | List of rule set entries |
| Field | Required | Description |
|---|---|---|
id |
✅ | Unique identifier for this entry |
schema |
optional | Relative path to the field schema YAML file |
actions |
optional | Relative path to the action schema YAML file |
rules |
✅ | List of relative paths to .rule files. The list order defines execution order — rules are evaluated file by file in this order, then in declaration order within each file, and matches are returned in that order. |
scope |
optional | Name of a declared collection. The entry’s rules run once per member of it instead of once for the whole document. |
Without scope, a rule set is evaluated once against the whole document — the default, and what
every manifest written before this key means.
entries:
- id: account-review
scope: accounts
schema: schema.yaml
rules:
- rules/exposure.rule
A scoped entry’s rules are written from one member’s point of view and name the member’s own
fields directly (balance, not accounts.balance). A name the member does not carry resolves
against the document, so a rule can still read a shared threshold or watch list.
The result keeps matches as one flat list in member order, each tagged with the member it came
from, and adds one members entry per member carrying that member’s own variables and
stoppedBy — a stop ends one member’s run, not the fan-out.
Rejected at load time when scope names no field, or names a field that is not a collection.
name: transaction-rules
entries:
- id: transactions
schema: schemas/transaction-schema.yaml
actions: schemas/actions.yaml
rules:
- rules/classification.rule
- rules/fraud-detection.rule
With this manifest, the engine loads:
schemas/transaction-schema.yamlschemas/actions.yamlrules/classification.rule and rules/fraud-detection.ruleA manifest can contain multiple entries. Each entry is completely independent — it has its own schema, action schema, and rules. This is useful when different parts of a system operate on different data models or have separate rule sets.
name: banking-rules
entries:
- id: transaction-classification
schema: schemas/transaction-schema.yaml
actions: schemas/transaction-actions.yaml
rules:
- rules/transactions/classification.rule
- rules/transactions/chargebacks.rule
- id: customer-risk
schema: schemas/customer-schema.yaml
actions: schemas/customer-actions.yaml
rules:
- rules/customers/risk-scoring.rule
- rules/customers/vip-detection.rule
Each entry is loaded and evaluated independently.
The order of the rules: list is the order the engine evaluates in — this list first, then the order the
rules are declared inside each file. That is a guarantee, not an implementation detail.
For plain rules the order is only visible in the results: every rule is checked against every record, and the order decides in which order the matches come back.
Two constructs make it part of the meaning, so that reordering the list changes the outcome rather than just its sequence.
A variable: a set clause makes its value available to the rules
after it in this list.
entries:
- id: orders
schema: schema.yaml
actions: actions.yaml
rules:
# First: publishes $orderTotal, which the rules below read.
- rules/totals.rule
- rules/pricing.rule
- rules/routing.rule
Moving totals.rule down would make every $orderTotal read a forward reference, and the build would
fail with reads unknown variable '$orderTotal'.
Variables are scoped to a single entry and a single evaluation. Two entries never see each other’s variables, and nothing carries over from one input record to the next.
The visual editor follows the same list. With a manifest open it validates the file you are editing
against the variables every file listed before it publishes, so a final rule file whose whole job is
to read what the earlier files accumulated validates in the editor exactly as it does at load time. A
file’s own forward reference is still reported: a set in a file listed after the open one is not in
scope there either.
A stop makes the order load-bearing in a second way: the rules listed after the rule that stops are not
evaluated at all. Move a guard rule down and it silently stops guarding the rules that were above it.
Comment the intent, the same way as for a variable:
entries:
- id: orders
schema: schema.yaml
actions: actions.yaml
rules:
# First: sanctioned-country guard, ends the run — everything below assumes it passed.
- rules/sanctions.rule
- rules/pricing.rule
Here is a clean folder structure for a rule project using a manifest:
my-rules/
├── manifest.yaml
├── schemas/
│ ├── transaction-schema.yaml
│ └── actions.yaml
└── rules/
├── classification.rule
├── fraud-detection.rule
└── chargebacks.rule
The manifest.yaml would then look like:
name: my-rules
entries:
- id: transactions
schema: schemas/transaction-schema.yaml
actions: schemas/actions.yaml
rules:
- rules/classification.rule
- rules/fraud-detection.rule
- rules/chargebacks.rule
The following is the manifest used in the full example project:
name: full-sample
entries:
- id: full
schema: full-schema.yaml
actions: actions.yaml
rules:
- rules/rent.rule
- rules/vip.rule
- rules/fraud.rule
And for the KLS legal-affairs use case:
name: kls
entries:
- id: legal_affairs
schema: fields_transaction.yaml
actions: actions_transaction.yaml
rules:
- label_de_legal_affairs_1.rule
When the engine loads a manifest, it performs the following checks:
$variable a rule reads is assigned by a set clause in an earlier rule of the same entry.If any check fails, the engine reports detailed errors and does not start.
You can run the same checks without starting anything, with ValidatorCli in manifest mode:
java -cp "<runtime classpath>" ruleengine.cli.ValidatorCli \
--manifest rules/manifest.yaml [--entry <id>] [--format json]
It validates the entry file by file in manifest order, so each diagnostic names the rule file it came
from and every variable resolves exactly as it will at load time. Prefer it over --schema + --rules
for anything that has a manifest: the directory mode cannot know the file order, and checks action names
only when you also pass --actions.
transaction-schema-v2.yaml) — when you change a schema, you can update the manifest reference without breaking existing files.transactions is more informative than entry1.