Skip to content

Function: not()

@kortexya/reasoninglayer


@kortexya/reasoninglayer / not

not(clause): PsiTermInputByName

Defined in: src/builders/psi.ts:225

Negate an antecedent — negation as failure over the tenant’s facts.

PsiTermInput

The pattern that must NOT be provable, from psi.

PsiTermInputByName

The negation carrier term to put in a rule’s antecedents.

ValidationError when clause is itself a negation carrier.

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.

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'))),
],
});