Fork me on GitHub
#clara
<
2022-10-24
>
ixemad07:10:27

Hi clara users! How is it possible that the same rule activates several times with all the bound variables being (structurally) the same? (no retracts, meanwhile) I'm using the http://www.clara-rules.org/apidocs/0.12.0/clojure/clara.tools.inspect.html#var-explain-activations function to try to figure out why it happens but I'm still puzzled 😒.

ethanc13:10:42

@plastermoso, im not sure i follow. Do you have a small example of what the rule in question is?

ixemad13:10:33

Sure. This is a tool I'm developing to scan an AWS platform. The following rule is activated for the same identity (who), provider, region and Policy document exactly 4 times. Not sure why it happens.

(defrule r-list-lambda-functions
  ;; Executor identity
  [?who <- ]
  [?provider <- ]
  [ (= :caller-identity (:label this))
             (= (:crn ?provider) (:l this))
             (= (:crn ?who     ) (:r this)) ]

  ;; Region in which to list lambda functions
  [?region <- ]

  ;; This action is explicitly allowed by a 
  ;; policy document linked to this identity
  [?d2 <- b.w.md/PolicyDocument]
  [b.md/Pair (= :is-entitled-by (:label this))
             (= (:crn ?who) (:l this))
             (= (:crn ?d2 ) (:r this)) ]
  [:test (is-action? ?d2 :allow "lambda:ListFunctions") ]
   =>
  (let [task (  {:domain 'bxt.aws.core
                                      :action 'list-lambda-functions
                                      :input {:region ?region}
                                      :auth-with ?provider
                                      :run-by (:crn ?who) } ) ]
    (t/debug "Rule «list-lambda-functions» fired with parameters"
             "\n?who     " ?who
             "\n?provider" ?provider
             "\n?region  " ?region
             "\n?d2      " ?d2 )
    (b.ru/emit-action task) ) )

mikerod16:10:19

2 possibilities: 1) You have multiple instances of at least one of the LHS fact(s). Even if it they are =, you can have multiple. Clara engine doesn’t attempt to merge = instances into a single instance. There are times that people want rules that actually rely on “counting instances” sort of logic, rather than having some always distinct (via =) set of facts in memory. 2) You have rule logic changes that are triggering the truth maintenance system (aka TMS) to retract previously invalidated rules. There is no direct guarantee that a rule RHS will not be re-evaluated if the TMS needs to re-evaluate the rules due to working memory changes during 1 or more fire-rules loops.

mikerod16:10:42

There are ways to mitigate these issues for your specific problem. I think understanding which applies to you may be a good start though.

mikerod16:10:21

If you really don’t care about anything other than not being called multiple times (maybe it has side-effects), I’d generally propose that you guard within that fn itself. Ensure that it is idempotent.

mikerod16:10:33

If that isn’t easily achievable, then model your RHS instead by inserting a fact that represents the action to take, rather than directly performing the action. Then external to the rules, have your calling application call a query on these “action facts” and then do the action based on those fact details retrieved. Doing so in a query outside of the fire-rules loop will give you stability against the TMS.

ixemad16:10:06

Thanks @U0LK1552A for your comprehensive reply. I was assuming that inserting a fact that was equal to another one already in the working memory did not have any effect. I can easily check that. All your other suggestions are also really valuable 💯

👍 1
ixemad17:10:22

Once again, thank you @U0LK1552A. Scenario 1) was the one was driving me crazy. Everything is fine now 🙌

👍 1
mikerod18:10:05

Ok, good. There are ways to mitigate this “multi-cardinality” of = fact issue. Accumulators, such as (cc/all) can be used in a rule to aggregate eg. all facts with some particular set of attribute values (maybe all the same), and then you get a single collection of them on the RHS, where you can do something like (first ?coll) to know that you only have that RHS happen “once” - it can still happen multiple times if TMS has truth retraction/insert cycles during the fire-rules loop, but that didn’t see to be your original case.

ixemad18:10:05

I'm also trying to use :not operator to test that a given action was not already executed in the AWS account but it was not working as expected. Maybe I can use (cc/all) to get a similar effect.

mikerod18:10:38

@plastermoso I found an old gist I had that is somewhat related to this topic - abstractly: https://gist.github.com/mrrodriguez/09df52e5f9729992c61f21fc638a20e6