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}]}