polix.operators
Extensible operator system for policy constraints.
Operators define how constraints are evaluated, simplified, and negated. Users can register custom operators to extend the policy DSL.
Built-in Operators
Comparison: :=, :!=, :>, :<, :>=, :<= Set membership: :in, :not-in Pattern matching: :matches
Defining Custom Operators
Use defoperator to define new operators:
(defoperator :starts-with
:eval (fn [value expected] (str/starts-with? value expected))
:negate :not-starts-with)
(defoperator :not-starts-with
:eval (fn [value expected] (not (str/starts-with? value expected)))
:negate :starts-with)
Or use register-operator! for programmatic registration:
(register-operator! :my-op
{:eval (fn [value expected] ...)
:simplify (fn [constraints] ...)
:negate :my-op-negated})
clear-registry!
(clear-registry!)Clears all registered operators. Useful for testing.
defoperator
macro
(defoperator op-key & {:keys [eval negate flip simplify subsumes?]})Defines and registers an operator.
Example:
(defoperator :starts-with
:eval (fn [value expected] (str/starts-with? value expected))
:negate :not-starts-with)
(defoperator :between
:eval (fn [value [low high]] (and (>= value low) (<= value high)))
:simplify (fn [cs] {:simplified cs}))
(defoperator :my-greater-than
:eval (fn [value expected] (> value expected))
:negate :my-less-or-equal
:flip :my-less-than)
eval-constraint
(eval-constraint constraint value)Evaluates a constraint against a value using the registered operator.
Returns true if satisfied, false if contradicted, nil if operator unknown.
eval-in-context
(eval-in-context ctx constraint value)Evaluates a constraint using operators from context.
flip-op
(flip-op op-key)Returns the flipped operator keyword for reversed operands.
Used when the operand order is reversed (e.g., [:> 5 :doc/x] becomes [:< :doc/x 5]). Returns op-key unchanged for symmetric operators like := and :!=.
(flip-op :>) ;=> :<
(flip-op :<) ;=> :>
(flip-op :>=) ;=> :<=
(flip-op :=) ;=> := (symmetric)
get-operator
(get-operator op-key)Returns the operator for op-key, or nil if not found.
get-operator-in-context
(get-operator-in-context ctx op-key)Gets operator from context, checking context operators, registry, then fallback.
IOperator
protocol
Protocol for constraint operators.
Import this namespace with :as op to use op/eval, op/negate, etc.
members
eval
(eval this value expected)Evaluates whether value satisfies the constraint with expected. Returns true, false, or nil if evaluation is not possible.
negate
(negate this constraint)Returns the negated form of constraint. May return a new constraint or nil if negation is not supported.
simplify
(simplify this constraints)subsumes?
(subsumes? this c1 c2)Returns true if constraint c1 subsumes c2 (c1 implies c2). Used to eliminate redundant constraints.
make-context
(make-context)(make-context {:keys [operators fallback strict? trace?], :or {strict? false, trace? false}})Creates an operator context for evaluation.
Options: - :operators - operator map (overrides registry for these keys) - :fallback - (fn [op-key]) for unknown operators - :strict? - throw on unknown operators (default false) - :trace? - record evaluation trace (default false)
negate-constraint
(negate-constraint constraint)Returns the negated form of a constraint, or nil if not supported.
negate-op
(negate-op op-key)Returns the negated operator keyword for op-key, or nil if not supported.
Uses the :negate key from the operator’s registration spec.
(negate-op :=) ;=> :!=
(negate-op :>) ;=> :<=
(negate-op :in) ;=> :not-in
operator-keys
(operator-keys)Returns all registered operator keys.
register-builtins!
(register-builtins!)Registers all built-in operators.
register-operator!
(register-operator! op-key spec)Registers an operator in the global registry with spec validation.
op-key is the operator keyword (e.g., :=, :my-app/custom-op).
spec is a map with required and optional keys: - :eval - (required) (fn [value expected] -> boolean?) evaluation function - :negate - keyword of the negated operator - :flip - keyword of the flipped operator for reversed operands - :simplify - (fn [constraints] -> {:simplified [...]} | {:contradicted [...]}) - :subsumes? - (fn [c1 c2] -> boolean?) subsumption check
Throws if spec is invalid (e.g., missing :eval or wrong types).
Note: Each registration increments the registry version, which may invalidate bytecode-compiled policies that use custom operators.
registry-version
(registry-version)Returns the current registry version.
The version is incremented each time an operator is registered. This enables bytecode-compiled policies to detect when the operator set has changed and fall back to interpreted evaluation.
simplify-constraints
(simplify-constraints op-key constraints)Simplifies constraints with the same operator on the same key.
validate-operator-spec!
(validate-operator-spec! op-key spec)Validates operator spec against schema, throws on invalid.