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 remain
  • nil — 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.