polix.negate
Policy negation through AST transformation.
Provides pure transformation of policy ASTs to their logical negations. Negation enables computing what would contradict a policy, useful for generating counter-examples and reasoning about policy violations.
Constraint-level negation is handled by polix.operators/negate-constraint. This namespace handles policy-level (AST) transformations:
- De Morgan’s laws for
:and/:or - Quantifier swapping (
:forall↔:exists) - Double negation elimination
- Delegation to operator negation for comparisons
Non-invertible expressions produce ::complex marker nodes.
complex-marker
(complex-marker original-node reason)Creates a complex marker node for non-invertible expressions.
Complex markers indicate that the expression cannot be meaningfully negated and should be treated as opaque during further processing.
complex?
(complex? node)Returns true if the node is a complex marker.
double-negate
(double-negate ast)Applies double negation to an AST.
Useful for testing that negation is self-inverse. Note that double negation may not produce an identical AST due to simplifications like :not elimination, but should be logically equivalent.
has-complex?
(has-complex? ast)Returns true if the AST contains any complex (non-invertible) markers.
Traverses the entire AST tree to check for complex nodes.
negate
(negate node)Negates an AST node, returning the negated AST.
Recursively transforms the AST according to negation rules:
- Function calls: De Morgan for
:and/:or, unwrap:not, delegate operators - Quantifiers: swap
:forall↔:existswith negated body - Literals/accessors: cannot be negated, produce complex markers
Returns a new AST node representing the negation.
Examples (showing logical equivalence, not exact AST structure):
;; Comparison operators
(negate (parse [:= :doc/role "admin"]))
;; equivalent to: [:!= :doc/role "admin"]
;; De Morgan's laws
(negate (parse [:and [:= :doc/x 1] [:= :doc/y 2]]))
;; equivalent to: [:or [:!= :doc/x 1] [:!= :doc/y 2]]
;; Quantifier swapping
(negate (parse [:forall [u :doc/users] [:= :u/active true]]))
;; equivalent to: [:exists [u :doc/users] [:!= :u/active true]]