;; lib/mod/engine.sx — decide a report by querying the policy program. ;; ;; build-program assembles the report's facts plus the compiled policy clauses; ;; decide-report runs the Prolog query and returns a decision. A decision is a ;; proof, not a bare keyword: it carries the matching rule, the conditions it ;; required, the evidence that satisfied them, and a derivation — the proof tree. ;; ;; The proof tree is built constructively: for the matching rule, each body goal ;; is re-queried against the same DB with the report id bound, recording the goal ;; text, whether it was solved, and the bindings that satisfied it. That is a ;; genuine derivation drawn from the Prolog database, ready for the audit trail. (define mod/find-rule (fn (rules name) (reduce (fn (acc r) (if (nil? acc) (if (= (mod/rule-name r) name) r acc) acc)) nil rules))) (define mod/build-program (fn (r count rules) (str (mod/report-facts r count) "\n" (mod/rules->program rules)))) (define mod/proof-goals (fn (db id conds) (if (empty? conds) (list {:solved true :goal "true" :bindings {}}) (map (fn (c) (let ((g (mod/cond->goal c id))) (let ((sols (pl-query-all db g))) {:solved (if (empty? sols) false true) :goal g :bindings (if (empty? sols) {} (first sols))}))) conds)))) (define mod/decide-report (fn (r reports rules) (let ((count (mod/report-count (mod/report-about r) reports)) (kinds (mod/classify-keywords r)) (id (mod/report-id r))) (let ((program (mod/build-program r count rules))) (let ((db (pl-load program))) (let ((sol (pl-query-one db (str "policy_action(" id ", Action, Rule)")))) (if (nil? sol) {:action "keep" :proof {:goals (list) :evidence kinds :conditions (list) :rule "none" :count count} :report-id id :rule "none"} (let ((rname (dict-get sol "Rule"))) (let ((rule (mod/find-rule rules rname))) {:action (mod/rule-action rule) :proof {:goals (mod/proof-goals db id (mod/rule-when rule)) :evidence kinds :conditions (mod/rule-when rule) :rule rname :count count} :report-id id :rule rname})))))))))