Skip to content

Variable: Constraint

@kortexya/reasoninglayer


@kortexya/reasoninglayer / Constraint

const Constraint: object

Defined in: src/builders/constraint.ts:57

Builders for the chainer’s homoiconic meta-constraints. Each function returns a single TermInputArg representing one constraint term — drop the result into a clause body or rule antecedents.

readonly aggregate(op, options): TermInputArg

Aggregate over every solution of a goal, and bind the result.

"count" | "min" | "max" | "sum" | "avg"

The aggregation — count, sum, min, max, avg.

The goal, where the result lands, the variable being aggregated, and an optional grouping variable.

TermInputArg

The goal whose solutions are aggregated.

string

Aggregate once per distinct value of this variable.

string

The variable that receives the aggregate.

string

The variable being aggregated. Omit for count.

TermInputArg

An aggregate_constraint term.

A BODY aggregate, over the solutions of one goal. The HEAD aggregate — a rule that derives one conclusion per group — is AddRuleRequest.aggregator, and is a different mechanism.

variable, groupBy and result must be three DIFFERENT variables: the evaluator identifies each by position, and aliasing two of them makes it read one role as another. count is the one op that needs no variable.

// ?Total is the sum of ?Amount over every line of the order.
Constraint.aggregate('sum', {
goal: psi('order_line', { order_number: '?No', amount: '?Amount' }),
variable: '?Amount',
result: '?Total',
})

readonly cut(): TermInputArg

Commit to the choices made so far in this clause.

TermInputArg

A cut term.

Prolog’s !. Everything to its left in the body keeps its current bindings and is not retried; alternative clauses for the same conclusion are not tried either.

readonly equation(result, op, left, right): TermInputArg

State that result = left op right. Selects the chainer sort by op: + → plus_constraint, - → minus_constraint, * → times_constraint, / → div_constraint. The chainer reads each operand as either a Reference variable or a numeric literal, and can run forward, reverse, or verify depending on which operands are bound.

Operand

Result variable or value (z).

ArithmeticOp

Arithmetic operator.

Operand

Left operand (x).

Operand

Right operand (y).

TermInputArg

The appropriate arithmetic-constraint term.

readonly findall(template, goal, results): TermInputArg

Collect every solution of a goal into a list.

TermInputArg

What to collect per solution — usually a variable the goal binds.

TermInputArg

The goal to enumerate.

string

The variable that receives the list.

TermInputArg

A findall_constraint term.

Succeeds with an empty list when the goal has no solutions, which is what separates it from stating the goal directly.

Constraint.findall('?Name', psi('employee', { name: '?Name' }), '?Everyone')

readonly forall(cond, action): TermInputArg

State that an action holds for EVERY solution of a condition.

TermInputArg

The condition to enumerate.

TermInputArg

What must hold for each of its solutions.

TermInputArg

A forall_constraint term.

Vacuously true when the condition has no solutions — the usual reading of universal quantification, and worth stating because it is the usual surprise.

// Every task of the project is done.
Constraint.forall(
psi('task', { project_code: '?P', key: '?K' }),
psi('done_task', { key: '?K' }),
)

readonly has(variable, feature, target): TermInputArg

State that a variable’s feature equals a target. The chainer unifies the variable’s feature value with the target.

Important: the chainer’s feature_constraint only unifies term-references with term-references. The variable’s feature on the persisted side must itself be a Value::Reference. Comparing against literals (e.g. has('?N', 'name', 'S') where name is stored as a plain string) silently returns zero solutions — that’s a chainer design constraint, not a builder bug.

string

The owning variable (e.g. '?Edge').

string

The feature name (e.g. 'src').

Operand

The target — typically another variable ('?Src') or an existing term referenced by its UUID via '!<uuid>'.

TermInputArg

A feature_constraint term.

readonly intRange(variable, min, max): TermInputArg

Constrain a variable to be an integer in the inclusive range [min, max]. Required before the chainer’s labeling step can assign a concrete value via Constraint.solveFor.

string

Variable name to constrain.

number

Lower bound (inclusive).

number

Upper bound (inclusive).

TermInputArg

An fd_domain_constraint term.

readonly not(goal): TermInputArg

State that a goal does NOT hold — negation as failure.

TermInputArg

The goal that must fail, as a Ψ-term.

TermInputArg

A negation term, for a rule body or a clause body.

Closed-world: the goal fails when nothing in the store proves it, which is not the same as the store proving its opposite.

The engine spells this as a negation term whose clause feature references the goal. Nothing in the SDK said so, and nothing in the OpenAPI description of AddRuleRequest says so either — a caller had to read TermStore::create_negation to find the shape. Hand-rolling it with a different key silently produces a rule with an antecedent the chainer does not recognise.

⚠️ Not expressible in OSFQL DERIVE at all (backend #244): the JSON rule surface is the only one that has it.

// An open invoice with no payment against it.
await client.inference.addRule({
term: psi('unpaid_invoice', { number: '?N' }),
antecedents: [
psi('invoice', { number: '?N', status: 'open' }),
Constraint.not(psi('payment', { invoice_number: '?N' })),
],
});

readonly once(goal): TermInputArg

Take only the FIRST solution of a goal.

TermInputArg

The goal.

TermInputArg

A once_constraint term.

Commits to the first solution and does not backtrack into the goal for more — the rule fires once per outer solution rather than once per inner one.

readonly solveFor(…variables): TermInputArg

Search for concrete integer assignments to the listed variables that satisfy every other constraint mentioning them. Place this at the END of a clause body — it’s the labeling/search step that turns a constraint network into solutions.

…string[]

Variable names to assign.

TermInputArg

An fd_labeling_constraint term.

readonly typeOf(variable, sortUuid): TermInputArg

State that a variable is a term of a given sort. The chainer enumerates every persisted term of that sort via backtracking.

string

Variable name (e.g. '?E') or pre-built variable Ψ-term.

string

UUID of the target sort (resolve from client.inference.getMetaSorts() or your own createSort response).

TermInputArg

A sort_constraint term.

readonly where(left, op, right): TermInputArg

State a boolean comparison between two operands. The chainer residuates the constraint until both sides are bound; when they are, the comparison must hold or the proof branch fails.

Operand

Left operand — variable, literal number, or term ref.

ComparisonOp

Comparison operator.

Operand

Right operand.

TermInputArg

A guard_constraint term.

These wrap the underlying meta-sort names (sort_constraint, feature_constraint, guard_constraint, plus_constraint, etc.) and use the exact feature keys the chainer reads. Hand-rolled psi('...') calls with guessed keys silently fail — use these builders instead.

import { Constraint, psi } from '@kortexya/reasoninglayer';
// "?E is a term of sort flow_edge_constraint, with edges leaving X"
const body = [
Constraint.typeOf('?E', flowEdgeSortUuid),
Constraint.has('?E', 'src', '?X'),
Constraint.has('?E', 'dst', '?Y'),
Constraint.has('?E', 'cap', '?C'),
Constraint.intRange('?Amount', 1, 100),
Constraint.where('?Amount', '<=', '?C'),
Constraint.solveFor('?Amount'),
];