Skip to content
Talk to our solutions team

Configuration

rules.svc boots from one YAML document, like every platform block. The document says where the service listens, how it checks tokens, which tenants it serves and where each one’s product comes from, and, under rules:, how the service bounds what the rules do. What rules a tenant runs is not configuration: it is the product’s rules/ folder, read through meta.

LayerWhat goes thereOwner
Boot documentlistener, TLS, token issuers, the tenants served, the rules: blockthe operator
Tenant nodethe tenant’s meta source, its data.svc endpoint, rules.svc’s own poolsthe operator
The product’s rules/ folderthe rules, vocabulary files, static scopes in rules/rules.yamlthe product team
The product’s rules/extend/access.yamlwho may run, evaluate, apply and author the rulesthe product team
The requestthe scope to run and the factsthe caller

The boot document is .rules.yaml in the working directory unless -f (or --config) names another, or comes from config.svc.

name: rules
host: 0.0.0.0
port: 8094
zerotrust: true
svccert: /etc/kisai/rules.crt
svckey: /etc/kisai/rules.key
log:
level: info
jwks:
issuers:
iam: "https://iam.example.com"
rules: # optional: every key has a default
exec:
timeout: 10s
maxtimeout: 30s
maxcycles: 5000
systemuser: # needed for data sources and background work
identity: rules-system
password: "vault://…"
sweeps:
enabled: false
events:
enabled: false
secret: "vault://…"
hives:
cluster:
- path: ""
tenants: # only these tenants are served
- "acme:prod:erp:eu1"
tenant:
- path: "acme:prod:erp:eu1"
name: eu1
active: true
meta:
url: "https://meta.example.com"
data: "https://data.example.com" # the tenant's data.svc, for `data` sources
datastores:
main: # the product's data, for `seshat` sources
dbpool: main
rules: # rules.svc's own records: sweeps
dbpool: rules
dbpools:
main:
type: postgres
dbserver: db.internal:5432
database: erp
dbuser: rules_reader # a read-only role is enough
password: "vault://…"
schema: acme_prod_erp_eu1 # data.svc's schema for the tenant
rules:
type: postgres
dbserver: db.internal:5432
database: erp
dbuser: rules_owner
password: "vault://…"
schema: rules_acme_prod_erp_eu1

The keys outside rules: are the ones every platform block reads: name, host, port, zerotrust with svccert and svckey (TLS, on by default), log.level, jwks (the issuers whose tokens are accepted), timeouts (read, write, readheader, shutdown), hives and loadtenants (tenants warmed at boot).

A closed key set. A key rules.svc does not read refuses boot, naming it (rules.svc does not read rules.exec.maxcycle), so a misspelt key never boots as its default. A cluster-configuration change is checked the same way; a refused document changes nothing and the one in place stays.

KeyDefaultMeaning
rules.exec.timeout10sThe longest one execution may run: a duration (10s) or seconds (10). Past it the call answers 504 rules-execution-timeout. A static scope’s limits.timeout replaces it for that scope
rules.exec.maxtimeout30sThe ceiling on a static scope’s limits.timeout: a longer one is cut to it. An exec.timeout above it refuses boot
rules.exec.maxcycles5000The most cycles one execution runs, and the ceiling on a static scope’s limits.maxcycles. An execution that needs more fails with 422 rules-execution-failed
rules.evaluate.maxids100The most records one POST /rules/evaluate takes
rules.body.maxbytes1048576The largest request body; a larger one answers 413 rules-body-too-large. A watched run’s draft has its own cap (4 MiB)
rules.systemuser.identity, rules.systemuser.passwordnoneThe system user, below. Both or neither
rules.sweeps.enabledfalseSweeps are off until an operator turns them on; a start answers 409 rules-sweeps-disabled until then
rules.sweeps.pertenant1Sweeps a tenant may have queued or running at once; one more answers 429 rules-sweep-limit
rules.sweeps.maxerrors100A sweep stops, failed, after this many records failed
rules.sweeps.pagesize500Records a sweep reads, decides and records at a time
rules.sweeps.retention720hHow long finished sweeps and what they recorded are kept; older ones are purged hourly
rules.events.enabledfalseTakes data.svc’s deliveries at POST /rules/events; while off, that route answers 404
rules.events.secretnoneThe secret data.svc signs each delivery with. enabled needs it

A watched run (POST /rules/run) keeps its own bounds whatever the block says: 10 seconds and 2000 cycles.

Some work has no caller to run as: a sweep after it starts, an evaluation a data change triggers, and the checks rules.svc makes of a data source when a tenant’s rules load. That work runs as the system user: a normal user of each tenant, created in iam with a password and no second factor, whose identity and password rules.systemuser holds. rules.svc logs in as it at the tenant’s iam, keeps the token until shortly before it expires, and logs in again.

  • Its privileges are granted like any user’s and bound what background work can do. A sweep checks them again before every page, so revoking one stops the sweep.
  • A product whose scopes read a data source does not load without a system user. Sweeps and events act as it, so they cannot run without one either.
  • Keep its password in vault (vault://…), as for any credential in the document.

rules.svc serves the tenants listed under the cluster node’s tenants:. For each, it builds a meta client from the tenant node (meta.url, or meta.localdir for a product checked out on disk), loads the product’s rules/ folder, and watches it.

Tenant node keyRead for
meta.url, meta.localdirwhere the product, and so its rules/ folder, comes from
datathe tenant’s data.svc, for scopes with a data source. When the tenant names none, rules.svc’s own data key is used
datastores.<name> with dbpools.<pool>for scopes with a seshat source: rules.svc’s own read pool on the tenant’s product data, naming data.svc’s schema for the tenant. A read-only database role is enough: a seshat source only reads
datastores.rules with its poolrules.svc’s own records (sweeps and what they recorded). rules.svc migrates this schema itself, and only its own tables
StateAnswer
The tenant is not configured400 rules-tenant-not-served
Configured, its first load not yet done503 rules-tenant-not-loaded, with Retry-After
Loadedserved; a later load that fails keeps what was served, and GET /rulesets says why

Readiness lists each tenant as a dependency (tenant:<key>) without gating on it, so one tenant’s broken folder never takes the instance out of rotation for the others. A tenant removed from the configuration keeps its rules until the process restarts.

rules/
rules.yaml static scopes (optional)
extend/
access.yaml who may do what (optional; holds no rules)
invoices/ a folder of rules, any depth
route.grl
limits/large.grl
vocab.yaml vocabulary for this folder; a subfolder's lists override its parent's
common/
currency.grl

A call runs a static scope of rules/rules.yaml by name, or a top folder with the folders under it. Scopes is the full reference for rules.yaml; Access for extend/access.yaml.

A load checks the whole folder before serving any of it, and fails, naming the file (and the rule and line for a rule), when:

  • a .grl sits directly in rules/ or in rules/extend/;
  • rules.yaml has an unknown key, a wrong type, a bad or repeated name, a path that is not there, or a declaration that does not hold (Scopes);
  • extend/access.yaml has an unknown action or key, or an empty expression;
  • a rule does not parse, or calls a method that does not exist or with the wrong number of arguments;
  • a static scope’s source cannot be opened or does not offer what the scope does with it.

kis rules check runs the same checks offline, except opening sources, and prints the same errors, so a broken folder is caught before it is pushed.

Both are off by default.

  • Sweeps need rules.sweeps.enabled, a system user, and the tenant’s rules datastore. See Sweeps.
  • Events need rules.events.enabled and secret, a system user, and data.svc delivering to rules.svc: an events.targets entry in data.svc’s configuration with url: https://rules.example.com/rules/events, the same secret, and the topics of the entities your scopes react to. See Events.
What changedWhat happens
A file under a tenant’s rules/that tenant’s rules load again; served whole when the load succeeds, kept as they were when it fails
The cluster configurationthe rules: block is checked and installed again (a refused one changes nothing); the tenants list is applied
A tenant nodethe tenant’s lifecycle restarts on its new settings

A sweep that is running keeps the rule text it started with; an edit mid-sweep does not change it.

  • A key under rules: that rules.svc does not read, or a value it cannot take: an exec.timeout or exec.maxtimeout that is not a positive duration, an exec.timeout over exec.maxtimeout, a count or a sweeps.retention that is not positive, events.enabled without events.secret, a system user with only one of identity and password.
  • Everything that refuses boot for every platform block: no port, certificates missing while zerotrust is on.