Function: not()
@kortexya/reasoninglayer / not
Function: not()
Section titled “Function: not()”not(
clause):PsiTermInputByName
Defined in: src/builders/psi.ts:225
Negate an antecedent — negation as failure over the tenant’s facts.
Parameters
Section titled “Parameters”clause
Section titled “clause”The pattern that must NOT be provable, from psi.
Returns
Section titled “Returns”The negation carrier term to put in a rule’s antecedents.
Throws
Section titled “Throws”ValidationError when clause is itself a negation carrier.
Remarks
Section titled “Remarks”Serialization format: untagged TermInputDto. A negated antecedent is
not a flag — it is an inline term of the reserved meta-sort negation whose
clause feature carries the probed pattern:
{"sort_name":"negation","features":{"clause":{"sort_name":"payment","features":{"invoice_number":{"name":"?N"}}}}}That is the shape POST /api/v1/inference/rules accepts, measured on
2026-09-18. GET /api/v1/inference/rules/{tenant} reads it back as a body
entry of sort_name: "negation" whose clause feature is the nested pattern
term, display: "NOT EXISTS payment(invoice_number: ?N)", and gives the rule
the OSFQL spelling
DERIVE unpaid(number: ?N) WHEN invoice(number: ?N), NOT EXISTS payment(invoice_number: ?N);.
One pattern per negation. The chainers read the carrier exactly one
constraint deep. A nested not(not(x)) IS accepted by the write route and
renders NOT EXISTS NOT EXISTS …, but derives nothing — measured, a rule
with a doubly negated antecedent produced zero rows where single negation
produced one — so this builder refuses it rather than let a rule fail
silently.
The negated clause binds nothing. Every head variable must also appear in a
POSITIVE antecedent, or the rule is refused
422 rule rejected: head variable(s) ?N not bound by any antecedent.
A negation about a bound term. Wrap the clause in bind to name
the term the negation is ABOUT: not(bind('?T', psi('meat_topping'))) is
the OSFQL NOT EXISTS ?T: meat_topping(), “the topping ?T is not a meat
topping”. The binder goes out as the nested clause’s binding, and the
chainers test the one term a positive antecedent bound to ?T, not every
meat_topping. A positive antecedent must bind ?T; the engine refuses
the rule with 422 otherwise, because negation as failure binds nothing.
Example
Section titled “Example”import { bind, not, psi } from '@kortexya/reasoninglayer';
// "an invoice with no payment against it"await client.inference.addRule({ term: psi('unpaid', { number: '?N' }), antecedents: [ psi('invoice', { number: '?N' }), not(psi('payment', { invoice_number: '?N' })), ],});
// "a pizza with a topping that is not a meat topping"await client.inference.addRule({ term: psi('veggie_option', { pizza: '?P' }), antecedents: [ bind('?P', psi('pizza', { toppings: '?T' })), not(bind('?T', psi('meat_topping'))), ],});