Skip to content
Talk to our solutions team

Events

A static scope can react to its records changing in data.svc: a ticket is created, and the rules set its priority. data.svc delivers each change to rules.svc; rules.svc evaluates the record the change names, with apply, as the system user.

rules/rules.yaml
scopes:
- name: ticket-triage
paths: [triage]
sources:
shop: { kind: data, datastore: main }
data:
over: { source: shop, entity: ticket, as: ticket, select: [id, subject, priority], key: id }
writes:
- { source: shop, entity: ticket, ops: [update], fields: [priority], concurrency: last-write-wins }
on:
- { ops: [create] }
rule Urgent "an outage is urgent" salience 10 {
when
strings.IContains(in.Str("ticket.subject", ""), "outage")
then
act.Set("priority", "urgent");
}
rule Normal "everything else is normal" salience 5 {
when
!strings.IContains(in.Str("ticket.subject", ""), "outage")
then
act.Set("priority", "normal");
}
facts
{ "ticket": { "id": "t-1", "subject": "Checkout outage in EU", "priority": null } }
out
{}
actions
[ { "op": "set", "field": "priority", "value": "urgent", "rule": "Urgent", "cycle": 1 } ]

on needs data.over on a data source. Its ops are create, update and delete; a deleted record has nothing to evaluate, so a delete triggers nothing. on.fields is checked but does not narrow a trigger yet, because data.svc’s events do not say which fields changed.

Three pieces, all required:

  1. The entity declares its events in the product’s data model, so data.svc emits them:

    - name: ticket
    events:
    - name: changed
    operations: [create, update]
  2. data.svc delivers them to rules.svc, as one of its event targets, signed with a shared secret:

    # data.svc's configuration
    events:
    targets:
    rules:
    url: https://rules.example.com/rules/events
    secret: "vault://…"
    timeout: 5s
    topics: [entity.ticket]
  3. rules.svc takes them, with the same secret and a system user:

    # rules.svc's configuration
    rules:
    events:
    enabled: true
    secret: "vault://…"
    systemuser:
    identity: rules-system
    password: "vault://…"
  1. rules.svc checks the delivery’s signature against the shared secret and its timestamp against the clock (within five minutes). The route takes no token and no tenant headers: the tenant is the one the signed event names.
  2. It finds every static scope of that tenant whose over entity is the event’s and whose on lists the operation.
  3. For each, it reads the record as the system user, with its related records and knowledge, runs the rules, and applies what they propose, exactly as evaluate with apply: true does. The system user must hold apply on the scope (Access); a scope it may not apply is skipped and logged.

A write rules.svc makes can itself be a change data.svc delivers. Two things end the chain:

  • A write the system user made triggers nothing. rules.svc’s own work never re-triggers a scope.
  • A decision applied twice writes nothing the second time. A field set to what it holds is dropped, so a record re-evaluated after a caller’s write settles.

Delivery is at least once, and safe to repeat for the same reason. rules.svc answers each delivery so the outbox knows what to do: data.svc delivers again after a 5xx, 408 or 429, and never after any other 4xx.

AnswerMeansdata.svc
204handled, or nothing to do: no scope reacts to it, the tenant is not served here, the system user wrote itdone
503, with Retry-Afterthe system user or a source cannot be reached nowdelivers again
401not signed with the secret, or its timestamp is not within five minutesdrops it
400not an event rules.svc can read: no tenant or entity, or a tenant that is not a customer:env:product:tenant keydrops it
404rules.events.enabled is offdrops it

A secret that differs between the two services, or events turned off in rules.svc while data.svc still delivers, loses those changes: check both when you rotate the secret.