Skip to content
Talk to our solutions team

`act`: proposing writes

act is how a rule asks for a change to product data. A rule never writes: each act call is a proposal, recorded with the rule and cycle that made it. What happens to proposals is the caller’s choice:

CallProposals
POST /rules/execute, POST /rules/run, kis rulesreturned as actions; never applied
POST /rules/evaluate, a sweepreturned as actions; applied only with apply: true
a data change a scope reacts toapplied, as the system user

A proposal is applied only when the static scope’s writes allow it, the caller may apply the scope, and the source lets the caller write that record. See Evaluating records.

MethodProposes
act.Set(field, value)a field of the record being evaluated: an update of the scope’s over record
act.Update(source, entity, id, fields)fields of another record, named by its key
act.Create(source, entity, fields)a new record
act.Upsert(source, entity, keys, fields)a record created, or updated when one matches on keys, a comma-separated list of its fields
act.Delete(source, entity, id)a record deleted
act.Count()how many proposals the run has made so far

source is a source name the scope declares under sources; entity is an entity of that source; fields is a map, built with map.Of(key, value, …) in the call itself.

rule Senior "large invoices go to a senior reviewer, with a task" salience 10 {
when
in.Double("invoice.total", 0.0) > 1000.0
then
act.Set("review_tier", "senior");
act.Create("shop", "task", map.Of("id", "t-" + in.Str("invoice.id", ""), "subject", "review " + in.Str("invoice.id", "")));
out.Set("tier", "senior");
}
rule Auto "small invoices are approved" salience 5 {
when
in.Double("invoice.total", 0.0) <= 1000.0
then
act.Set("review_tier", "auto");
out.Set("tier", "auto");
}
rule Counted "a rule can read what was proposed" salience 1 {
when
act.Count() > 1
then
out.Set("proposed", act.Count());
}
facts
{ "invoice": { "id": "inv-big", "total": 1500.0 } }
out
{ "proposed": 2, "tier": "senior" }
actions
[
{ "op": "set", "field": "review_tier", "value": "senior", "rule": "Senior", "cycle": 1 },
{ "op": "create", "source": "shop", "entity": "task", "fields": { "id": "t-inv-big", "subject": "review inv-big" }, "rule": "Senior", "cycle": 1 }
]

out and act are separate: out is the decision the caller reads, actions the writes the rules want. A later rule sees each proposal as it is made, so act.Count() in a when reads the current count.

rule Others "a customer's other records" {
when
in.Has("customer.id")
then
act.Update("shop", "customer", in.Str("customer.id", ""), map.Of("segment", "gold"));
act.Upsert("shop", "contact", "customer_id,email", map.Of("customer_id", in.Str("customer.id", ""), "email", in.Str("customer.email", "")));
act.Delete("shop", "hold", in.Str("customer.hold", ""));
}
facts
{ "customer": { "id": "c1", "email": "[email protected]", "hold": "h-9" } }
out
{}
actions
[
{ "op": "update", "source": "shop", "entity": "customer", "id": "c1", "fields": { "segment": "gold" }, "rule": "Others", "cycle": 1 },
{ "op": "upsert", "source": "shop", "entity": "contact", "keys": ["customer_id", "email"], "fields": { "customer_id": "c1", "email": "[email protected]" }, "rule": "Others", "cycle": 1 },
{ "op": "delete", "source": "shop", "entity": "hold", "id": "h-9", "rule": "Others", "cycle": 1 }
]
  • Declared. The scope’s writes must allow the source, entity, operation and every field. A call whose names are written out is checked when the scope loads, and one the writes do not allow fails the load, naming the rule. A name the rule computes is checked when the record’s proposals are planned, dry or applied: the record answers with the error rules-write-not-declared, and none of its proposals is applied.
  • A record to set. act.Set updates the record being evaluated, so it needs a scope with data.over. Under execute there is no such record: the proposal comes back and nothing applies it.
  • Versions. An update or delete carries the version of the record it read and conflicts when the record changed since; the record is decided again once. An entity without versions needs concurrency: last-write-wins on its write.
  • No-ops are dropped. A field set to the value it already holds is not written, so applying the same decision twice writes nothing the second time.