8000 fix(magic-sets): guard a rule only by demand every call site supplies (#608) by adityamukho · Pull Request #609 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

fix(magic-sets): guard a rule only by demand every call site supplies (#608) - #609

Merged
adityamukho merged 1 commit into
v3from
fix/608-magic-sets
Oct 16, 2026
Merged

adityamukho merged 1 commit into
v3from
fix/608-magic-sets

Conversation

@adityamukho
Copy link
Copy Markdown
Collaborator

Issue #608: (chain ?x :c) failed with INT-022 when chain calls link with an argument nothing has bound yet.

Root cause

propagate_adornments gave each rule the adornment of the first call it saw ("first-writer-wins") and assumed every other call matched. The code comment said a mismatch costs "less pruning, not wrong results". That is not true. A rule guarded by a magic predicate derives only the demanded facts, so any call site that does not supply that demand gets one of two outcomes:

  • INT-022. In the Recursive rule calling another rule fails with INT-022 when its second argument is bound #608 query, (link ?x ?m) inside chain (adorned fb) emitted the propagation rule magic_link_fb(s, ?m) :- magic_chain_fb(s, ?y), and nothing in its body binds ?m.
  • Silently missing rows (found while investigating; the same on v3 before this PR):
    • (link ?x :c) (link ?p ?q) returned only the :c edges for ?p ?q.
    • Same for (link ?x :c) (any ?p ?q) when any calls link.
    • A call to link inside or or not saw only the demanded facts.
    • (link ?x :c) (chain :a :c) (a bb call: the seed and guard encodings disagree) returned nothing.
    • [?s :edge :b] (link ?s ?y) (chain ?q :c) failed with INT-022: link was guarded but never seeded.

Fix

solve_adornments replaces propagate_adornments and compute_query_adornments:

  • It starts every rule reachable from the query as all-bound.
  • Each round, a rule's adornment becomes its meet with every call site's adornment, then is normalized to one bound position (arg0 or arg1, the positions a seed or magic fact can carry). This repeats until nothing changes. A rule with no bound position left is evaluated unguarded.
  • A call site counts as bound only if it can carry the demand:
    • Query calls: bound only where the argument is a literal a seed can carry. A variable bound by an earlier pattern cannot be seeded.
    • Calls in a guarded rule's body (no negation): bound where a variable is already bound, or where a literal can be carried.
    • Calls inside or/or-join/not/not-join, and calls from unguarded rules: free.
  • Adornments only lose bound positions, so this terminates.

For #608, chain stays fb (pruned by the demand), and link is evaluated in full.

Tests

  • 7 integration tests in tests/magic_sets_test.rs, one per shape above. Each fails on v3 without this change (3 with INT-022, 4 with wrong rows) and passes with it.
  • magic_sets.rs unit tests for query_call_adornment, normalize, meet and the solved adornment of each shape.
  • Full suite: 1538 passed, 10 ignored. cargo clippy --all-features -- -D warnings is clean.

Not in scope

[?x :edge ?z] (or [?p :edge :b] [?p :edge :c]), an or whose variables nothing earlier binds, returns no rows even with only patterns and no rules. That is a separate issue and has not been filed yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HyLscCUuJ1GQpdAeX79EdR

…#608)

Adornments were taken from the first call to each rule and assumed for every
other call. A call that did not match — a rule-body call whose argument nothing
had bound yet, the same rule called with free arguments, a call inside or/not,
or a call from an unguarded rule — failed with INT-022 or silently got only the
demanded facts. solve_adornments now starts each reachable rule all-bound and
weakens it to the meet of every call site's adornment (one bound position,
arg0 or arg1) until a fixpoint; a rule left with none is evaluated unguarded.
Query calls are bound only where a literal can be seeded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyLscCUuJ1GQpdAeX79EdR
@codecov
codecov Bot commented Oct 16, 2026
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.00000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/query/datalog/magic_sets.rs 97.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

8000

@adityamukho
adityamukho merged commit b1e8b73 into v3 Oct 16, 2026
25 checks passed
@adityamukho
adityamukho deleted the fix/608-magic-sets branch October 16, 2026 06:49
adityamukho added a commit that referenced this pull request Oct 16, 2026
…#608) (#611)

Backport of #609 from v3. Adornments were taken from the first call to each
rule and assumed for every other call. A call that did not match — a rule-body
call whose argument nothing had bound yet, the same rule called with free
arguments, a call inside or/not, or a call from an unguarded rule — failed with
INT-022 or silently got only the demanded facts. solve_adornments now starts
each reachable rule all-bound and weakens it to the meet of every call site's
adornment until a fixpoint; a rule left with no bound position is evaluated
unguarded.


Claude-Session: https://claude.ai/code/session_01HyLscCUuJ1GQpdAeX79EdR

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0