Skip to content

Engine: rules with unbound variables in comparisons are accepted and derive wrong results #108

Closed

Description

@jsam

Summary

A rule whose comparison references a variable bound nowhere in the body is accepted at registration and the comparison is silently satisfied, so the rule derives rows it must not derive. Same wrong-results family as #91: no error, no log, just incorrect data.

Repro (verified live 2026-08-20)

+customer(id: int, name: string, tier: string)
+customer[(1, "ada", "gold"), (2, "bo", "silver"), (3, "cy", "gold")]
+gold_customer(name: string)
+gold_customer(Name) <- customer(_, Name, _), Tier = "gold"
?gold_customer(Name)

Expected: registration error (Tier is unbound - variable-safety violation), or at minimum zero derived rows. Actual: all three customers derived, including silver.

Impact

The Python SDK's rule compiler emitted exactly this shape for every Derived rule with a where() on a non-selected column (fixed client-side in PR #107), which means silently wrong derived relations in any app using that pattern. tools/lint_iql.py catches variable safety for pack files, but the engine itself must reject unsafe rules - SDK compilers and hand-written rules bypass the linter.

Suggested fix

Range-restriction/variable-safety check at rule registration: every variable in a comparison or negation must be bound by a positive body atom. Reject with a clear error, matching the linter's rule.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend/server relatedbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions