polix.compiler

Policy compilation with residual-based evaluation.

Compiles multiple policies into a single optimized function that returns one of three result types when applied to a document:

  • {} (empty map) — document fully satisfies all constraints
  • {:path [constraints]} — partial match with remaining constraints
  • {:path [[:conflict constraint witness]]} — constraint violated with witness

Example

(def checker (compile-policies
               [[:and [:= :doc/role "admin"] [:> :doc/level 5]]
                [:in :doc/status #{"active" "pending"}]]))

(checker {:role "admin" :level 10 :status "active"})
;; => {}

(checker {:role "guest"})
;; => {[:role] [[:conflict [:= "admin"] "guest"]]}

(checker {:role "admin"})
;; => {[:level] [[:> 5]], [:status] [[:in #{"active" "pending"}]]}

boolean-ops

Set of boolean connective operators.

comparison-op?

(comparison-op? op)

Returns true if op is a registered comparison operator (not a boolean connective).

compile-policies

(compile-policies policy-exprs)(compile-policies policy-exprs opts)

Compiles multiple policies into an optimized evaluation function.

Takes a sequence of policy expressions and optional options map. Returns a function that takes a document and returns one of:

  • {} — satisfied (empty residual)
  • {:path [constraints]} — open residual with remaining constraints
  • {:path [[:conflict ...]]} — conflict residual with violated constraints

Options: - :operators - custom operators map (overrides registry) - :fallback - (fn [op-key]) for unknown operators - :strict? - throw on unknown operators (default false) - :trace? - record evaluation trace (default false) - :optimized - enable optimized evaluation (default true)

Policies are merged with AND semantics - all must be satisfied.

The returned function accepts either: - (check document) - use compile-time context - (check document opts) - override context per-evaluation

When :trace? is enabled (at compile-time or per-evaluation), returns {:result <value> :trace [...]} where :result is the normal evaluation outcome and :trace is a vector of evaluation steps.

Optimized evaluation uses pre-computed residual templates for fast evaluation. Use :optimized false to disable and use interpretation.

Example:

(def check (compile-policies
             [[:= :doc/role "admin"]
              [:> :doc/level 5]]))

(check {:role "admin" :level 10})  ;; => {}
(check {:role "guest" :level 10}) ;; => nil
(check {:role "admin"})           ;; => {[:level] [[:> 5]]}

;; With tracing
(check {:role "admin"} {:trace? true})
;; => {:result {} :trace [{:op := :value "admin" :expected "admin" :result true}]}

constraint

(constraint key op value)

Creates a normalized constraint.

The key parameter is a path vector (e.g., [:role] or [:user :name]) representing the document path to constrain.

constraint?

(constraint? x)

Returns true if x is a Constraint.

cross-key-constraint?

(cross-key-constraint? x)

Returns true if x is a CrossKeyConstraint.

eval-constraint

(eval-constraint constraint value)

Evaluates a single constraint against a value using the operator registry.

Returns true if satisfied, false if contradicted, nil if operator unknown.

eval-constraints-for-key

(eval-constraints-for-key constraints value value-present?)

Evaluates all constraints for a key against a value. Returns :satisfied, :contradicted, or the constraints if value is nil.

evaluate-document

(evaluate-document constraint-set document)

Evaluates a simplified constraint set against a document.

Delegates to polix.unify/unify-constraint-set for unified evaluation.

Returns: - {} if all constraints are satisfied - {:path [constraints]} if some constraints cannot be evaluated (open) - {:path [[:conflict ...]]} if constraint violated with witness value

merge-constraint-sets

(merge-constraint-sets constraints)

Merges multiple constraint sets, grouping by key.

merge-policies

(merge-policies policy-exprs)

Merges multiple policy expressions into a single constraint set. All policies are ANDed together.

normalize-ast

(normalize-ast ast)

Converts a policy AST to a normalized constraint structure.

Returns a map with: - :op - :and, :or, :constraint, :quantifier, :value-fn, or :complex - :constraints - for :and/:or, vector of child structures - :constraint - for :constraint, the Constraint record - :negated - boolean for negated constraints - :ast - for :quantifier, :value-fn, and :complex, the original AST node

normalize-policy-expr

(normalize-policy-expr expr)

Normalizes a policy expression (vector DSL) to constraint structure.

residual->constraints

(residual->constraints residual)

Converts a residual map back to policy expressions.

Takes {[:level] [[:> 5]], [:user :status] [[:in #{"a" "b"}]]} Returns [[:> :doc/level 5] [:in :doc/user.status #{"a" "b"}]]

result->policy

(result->policy result)

Converts a unification result to a simplified policy expression.

Returns: - nil for {} (satisfied, no constraints needed) - [:contradiction] for legacy nil or conflict residuals - The simplified constraints for open residual

simplify-constraint-set

(simplify-constraint-set constraint-set)

Simplifies a constraint set (map of key -> constraints). Returns {:simplified {…}} or {:contradicted {:key … :reason …}}.

simplify-constraints

(simplify-constraints constraints)

Simplifies a list of constraints on a single key. Returns {:simplified constraints} or {:contradicted reason}.

with-trace

(with-trace compiled-fn document)

Evaluates a compiled policy with tracing enabled.

Returns {:result <value> :trace [...]} where :result is the evaluation outcome ({}, nil, or residual) and :trace is a vector of {:op :value :expected :result} maps for each constraint evaluated.

Example:

(def check (compile-policies [[:= :doc/role "admin"]]))
(with-trace check {:role "admin"})
;; => {:result {} :trace [{:op := :value "admin" :expected "admin" :result true}]}

(def check2 (compile-policies [[:= :doc/role "admin"] [:> :doc/level 5]]))
(with-trace check2 {:role "admin"})
;; => {:result {[:level] [[:> 5]]}
;;     :trace [{:op := :value "admin" :expected "admin" :result true}]}