polix.unify
Core unification engine for policy evaluation.
Unifies policies with documents, returning residuals that describe the remaining constraints after partial evaluation.
Result Types
Unification returns one of three result types:
{}(empty map) — satisfied, all constraints met{:key [constraints]}— residual, some constraints remainnil— contradiction, no document can satisfy the policy
Example
(require '[polix.unify :as unify]
'[polix.parser :as parser]
'[polix.result :as r])
;; Fully satisfied
(let [ast (r/unwrap (parser/parse-policy [:= :doc/role "admin"]))]
(unify/unify ast {:role "admin"}))
;; => {}
;; Contradiction
(unify/unify ast {:role "guest"})
;; => nil
;; Residual (missing data)
(unify/unify ast {})
;; => {[:role] [[:= "admin"]]}
Uses polix.residual for result construction and polix.operators for constraint evaluation.
get-binding
(get-binding ctx binding-name)Gets a binding value from context, or nil if not found.
path->doc-accessor
(path->doc-accessor path)Converts a path vector back to a doc accessor keyword.
(path->doc-accessor :role) ;=> :doc/role (path->doc-accessor :user :name) ;=> :doc/user.name
path-exists?
(path-exists? document path)Returns true if the full path exists in the document.
Unlike get-in, this distinguishes between a nil value at the path and a missing key. Returns true only if all keys in the path exist, even if the final value is nil.
(path-exists? {:user {:name "Alice"}} [:user :name]) ;=> true
(path-exists? {:user {:name nil}} [:user :name]) ;=> true
(path-exists? {:user {}} [:user :name]) ;=> false
(path-exists? {} [:user :name]) ;=> false
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"}]]
Also handles cross-key constraints: {::cross-key [{:op := :left-path [:a] :right-path [:b]}]} Returns [[:= :doc/a :doc/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
unify
(unify policy document)(unify policy document opts)Unifies a policy with a document.
Takes: - policy - AST node, constraint set, or policy expression vector - document - Associative data structure to evaluate against - opts - Optional map with: - :operators - operator overrides - :fallback - fallback operator lookup - :strict? - error on unknown operators - :registry - policy registry for resolving policy references - :params - parameter map for :param/ accessors - :self - self-reference map for :self/ accessors - :event - event data for :event/ accessors
Returns: - {} if fully satisfied - {:key [constraints]} if partial — open constraints or conflicts
Residuals may contain: - Open constraints like [:< 10] — awaiting evaluation - Conflict constraints like [:conflict [:< 10] 15] — evaluated and failed
Use polix.residual/has-conflicts? to check if the result contains conflicts.
Examples
;; Unify AST
(unify ast {:role "admin"})
;; => {}
;; Partial evaluation (missing data)
(unify ast {:role "admin"}) ; missing :level
;; => {[:level] [[:> 5]]}
;; Conflict (data present but violates constraint)
(unify ast {:role "guest"})
;; => {[:role] [[:conflict [:= "admin"] "guest"]]}
;; With registry for policy references
(unify [:auth/admin] {:role "admin"} {:registry my-registry})
unify-and
(unify-and results)Combines results with AND semantics.
Returns: - {} if all results are satisfied - Merged residual otherwise (may contain open and/or conflict constraints)
With the conflict model, all branches are evaluated to collect all constraints for diagnostic purposes. Uses polix.residual/merge-residuals for combining constraint maps.
unify-ast
multimethod
Unifies an AST node with a document.
Returns: - {} — satisfied - {:key [constraints]} — residual - nil — contradiction
unify-constraint
(unify-constraint ctx constraint value path)Unifies a single constraint against a value.
Takes an operator context ctx, a constraint map with :op and :value keys, the actual value from the document, and the path for residual construction.
Returns {} if satisfied, or a conflict residual if violated.
unify-constraint-set
(unify-constraint-set constraint-set document)(unify-constraint-set constraint-set document ctx)Unifies a constraint set against a document.
A constraint set is a map of {path -> [constraints], ::complex -> [nodes]}. Paths are vectors of keywords representing nested document access.
Returns: - {} if all constraints satisfied - nil if any constraint contradicted - Residual map if partial match
unify-constraints-for-key
(unify-constraints-for-key ctx constraints value value-present? path)Unifies all constraints for a key against a value.
Returns: - {} if all satisfied - Residual with open constraints if value missing - Residual with conflicts if any constraint violated
unify-not
(unify-not result)Negates a unification result.
- NOT satisfied → complex (we don’t know what to negate)
- NOT all-conflicts → satisfied (if everything failed, NOT succeeds)
- NOT open/partial → complex (cannot negate unknown)
The key insight: if a residual contains only conflicts (all constraints were evaluated and all failed), then NOT of that is satisfied.
unify-or
(unify-or results)Combines results with OR semantics.
Returns: - {} if any result is satisfied (short-circuit) - Complex residual with all branches otherwise
For OR, if any branch succeeds the whole succeeds. If all fail, we return a complex marker containing all the failure branches (which may include conflicts and/or open constraints).
Uses polix.residual/combine-residuals for OR combination.
with-binding
(with-binding ctx binding-name value)Adds a binding to the evaluation context.
Bindings are used by quantifiers to track the current element being iterated.