本文へ移動
cccskills
無料GitHub で公開

rfdb-datalog-hash-join-shared-var

Fix silent incorrect results in RFDB Datalog queries that share a variable between two edge atoms on opposite ends (e.g. self-join patterns like `edge(M, C, "T1"), edge(C, M, "T2")`). Use when: (1) a Datalog rule returns rows that look structurally wrong (e.g. a self-loop rule reports hits but pinning M to each reported id refutes the edge existence), (2) Cypher `MATCH (m)-[:R1]->(c)-[:R2]->(m)` returns the same bogus rows (engine-level bug, not Datalog-specific), (3) results change when you bind one variable explicitly vs. leave it free. Root cause: hash- join fast path in `eval_edge_hash_join` / `eval_incoming_hash_join` unconditionally rebinds the non-key Term::Var without checking whether the variable is already bound in the current row.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md4.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

RFDB Datalog hash-join shared-var bug

Problem

RFDB's Datalog engine has a fast path that kicks in once current bindings exceed HASH_JOIN_THRESHOLD: it pulls all edges of a given type and builds HashMap<src, Vec<dst>> (or the inverse for incoming) for O(1) lookups per binding.

The bug: when the other end of the edge is a Term::Var whose name is already bound in the current row (i.e. a shared variable across two atoms), the fast path rebinds it unconditionally. Any value that satisfies the second edge pattern in isolation gets emitted as a "match," even when it contradicts the first atom's bindings.

Repro

edge(M, C, "CONTAINS"), edge(C, M, "SENDS_MESSAGE")

When you want: M contains C AND C sends back to M (self-loop).

What you get with the bug: M contains C AND C sends to *any* M', bleeding ~N false positives.

Constraining M to a concrete id returns 0 rows; free M returns many. That delta is the symptom.

Trigger conditions

  1. Two edge/3 or incoming/3 atoms share a variable, each on opposite sides of the edge direction.
  2. Current bindings for the first atom exceed HASH_JOIN_THRESHOLD (default 256). Below that, nested-loop runs via unify which handles bound vars correctly.
  3. Edge types are constants (hash-join requires const type).

Fix

In packages/rfdb-server/src/datalog/eval.rs, both eval_edge_hash_join and eval_incoming_hash_join must check the non-key Term::Var against bindings.get(var) before accepting a row:

if let Term::Var(var) = dst_term {
    if let Some(existing) = bindings.get(var).and_then(|v| v.as_id()) {
        if existing != dst_id { continue; }
    }
}

(Mirror in eval_incoming_hash_join with src_term / src_id.)

Put the check before bindings.clone() so mismatched rows are skipped without allocation.

Verification

Regression tests in packages/rfdb-server/src/datalog/tests.rs:

  • test_hash_join_shared_var_across_two_edges — edge/3 self-join.
  • test_incoming_hash_join_shared_var — incoming/3 self-join.

Both build a graph with n = HASH_JOIN_THRESHOLD + 10 triples where exactly one pair forms a true round-trip. Without the fix, the rule returns n rows; with the fix, 1.

How we found it

Adding MESSAGE_TYPE → CONTAINS → CALL edges to the BEAM graph and writing a Datalog guarantee for handler self-loops surfaced 13 hits on Ichi. Drilling into each "hit" M by binding it to a literal id showed zero incoming CONTAINS-that-sends-back edges. Reducing to the minimal repro edge(A, B, "CONTAINS"), edge(B, A, "CONTAINS") on a tree-shaped graph produced 15 spurious symmetric pairs — a tree has zero symmetric CONTAINS by construction. Cypher showed the same rows, ruling out the Datalog reorder/parser layer and pointing at the shared join engine.

Notes

  • Nested-loop path (via unify) is correct — the bug is isolated to the hash-join fast path.
  • eval_negation_hash_join (for \+ edge(X, _, "T")) does not have the same bug because it only projects one variable by design; the other side is always _ / Wildcard.
  • If you ever add a third variant of hash-join (e.g. for path/2 transitive closure), port the same check.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Fix Elixir/Erlang AST processing bugs in Grafema beam-analyzer. Use when: (1) Elixir parser returns MODULE node but 0 functions/calls — body nesting issue, (2) Erlang parser crashes with "cannot convert list to string" on OTP 26+ — location format changed from integer to keyword list, (3) pipe operator |> creates spurious CALL nodes instead of desugared function calls — clause ordering bug, (4) multi-module .ex files return only the first module — missing __block__ handler, (5) installing Erlang/Elixir on macOS with outdated Xcode/Clang.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fires after fixing any non-trivial bug or regression. Asks: could the graph have caught this as a guarantee? Pairs with reflection-in-and-on-action — picks up after "earliest catchable signal" and asks the next question: was that signal expressible in graph? Triggers: (1) after any non-trivial bug fix is verified working, (2) after a regression report (something used to work, broke), (3) during step 6 (knowledge extraction) of the workflow, (4) when reflection-on-action surfaces a "would have been catchable" signal. Outcome is a triage decision (graph-reachable? rule expressible? rule sound?) and either a draft Linear issue + guarantee proposal, or a recorded "graph capability gap" note. Never auto-creates guarantees.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix Playwright automation failures against code-server (VS Code in browser). Use when: (1) trust dialog blocks all clicks — "monaco-dialog-modal-block intercepts pointer events", (2) button:has-text() finds wrong buttons behind modal dialog, (3) keyboard shortcuts don't work — Meta vs Control inconsistency, (4) VS Code extension activity bar icon not found by aria-label, (5) panels show placeholder text despite extension being "Connected". Covers trust dialog dismissal, keyboard shortcut hybrid mode, extension panel selectors, and database connection verification for Grafema extension.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Systematic methodology for achieving 100% backward dataflow reachability in a new language. Create gauntlet fixture, write trace, diagnose gaps, fix analyzer/algorithm, iterate to 100%. Language-agnostic process. Use when: (1) adding a new language to Grafema, (2) auditing dataflow coverage for existing language, (3) user says "/dataflow-gauntlet".

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix docker exec hanging when starting background processes (servers, daemons) inside containers. Use when: (1) docker exec never returns despite using & or nohup, (2) background server started in container causes docker exec to hang indefinitely, (3) env_startup_command in SWE-bench or similar frameworks times out, (4) setsid/disown needed for proper process detachment in Docker. Root cause: docker exec tracks ALL processes in the exec session, not just the top-level PID.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix Node.js CLI tools crashing inside Docker containers when host-installed node_modules require a newer Node version than the container provides. Use when: (1) "SyntaxError: Invalid regular expression flags" with /v flag in string-width or similar packages, (2) node_modules installed on host with Node 20+ but container has Node 18, (3) `npm install` with file: protocol creates symlinks that break inside Docker, (4) pnpm workspace packages become broken symlinks in containers. Covers version detection, Node binary mounting, and npm install strategies for cross-version compatibility.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Disentinel のスキルをすべて見る

このスキルの問題を報告する