8000 fix(storage): B+tree cursor refuses non-increasing keys; fuzz guard counts partial bindings (#597) by adityamukho · Pull Request #599 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

fix(storage): B+tree cursor refuses non-increasing keys; fuzz guard counts partial bindings (#597) - #599

Merged
adityamukho merged 6 commits into
v3from
fix/597-fuzz
Oct 15, 2026
Merged

adityamukho merged 6 commits into
v3from
fix/597-fuzz

Conversation

@adityamukho
@adityamukho adityamukho commented Oct 15, 2026 •
Copy link
Copy Markdown
Collaborator

Two failures from the nightly fuzz run in #597.

btree_page: out-of-memory, a scan that never finishes (fixed)

Input 80 10 04 01 00 05 sets an internal node's rightmost child to a page on its own path. live_walk resumes every batch with a new LeafCursor seeked past the last triple, so the cycle kept handing back leaves already read. Each batch returned the same keys, the cursor's 32-level depth guard was never reached, and the query ran until out of memory.

LeafCursor now enforces key order (INT-049 otherwise), once per leaf:

  • the first key of the next leaf must be greater than the last key of the leaf it left;
  • no key below the cursor's start or last seek target is returned;
  • an empty leaf below an internal node is refused (only an empty tree has one, as its root; build_btree/cow_insert never write one).

A seek to a key at or before the end of the current leaf no longer moves the cursor. verify() already reported such trees.

Tests:

  • 3 unit tests in storage::btree: cycle, stale leaf after a seek, empty leaf. All 3 fail without the fix.
  • tests/integrity_test.rs: redirects each internal node of a checkpointed 3,000-fact file to itself, and each query must fail or answer within 60 s. Without the fix it times out.
    • It's skipped under test-oracle. On a damaged file the engine and the executor read different pages: the executor also looks up each fact's tx timestamp in the DICT. So the executor can fail with INT-049 where the engine answers, and the oracle panics on that. This is expected on corrupt data, not an engine bug.

The input is added as a seed.

datalog_eval: timeout (harness)

The input is a not-join with a 120-pattern body. Negation bodies still run on the executor pattern by pattern in text order. The body builds hundreds of thousands of partial bindings before its last pattern (:__magic…) matches nothing, so it takes 65 s on current v3. This is the known #575 limit (streaming engine; #546 for negation), not a new bug. The guard counted only rows after the last pattern, which here is 0. It now counts the bindings after every pattern. Corpus coverage goes from 9,719 to 9,712 edges over 4,783 inputs. The input is added as a seed and now runs in about 140 ms.

main has the same guard; the harness change goes there as a separate PR. The cursor fix applies to v8 only, so there's no main backport.

Checks

  • cargo test: 1507 tests (1497 passing, 10 ignored)
  • cargo test --features test-oracle --no-fail-fast: 1496 passing, 11 ignored
  • cargo clippy --all-features -- -D warnings: clean
  • Both fuzz inputs rerun on the built targets: no OOM, no timeout
  • Codex review: no findings

Refs #597

🤖 Generated with Claude Code

https://claude.ai/code/session_014v6aUco55yj3UScjL28mUP

adityamukho and others added 3 commits October 15, 2026 08:45
The nightly btree_page fuzz run pointed an internal node's rightmost child
back at the node. A scan resumes each batch with a new cursor seeked past
the last key it read, so the cycle handed back leaves already read: every
batch returned the same keys, the depth guard was never reached, and the
query never finished, growing until out of memory.

LeafCursor now requires the first key of each leaf it moves to to be
greater than the last key of the leaf it left and not below its start or
last seek target, and refuses an empty leaf below an internal node (only
an empty tree has one, as its root). Each fails with INT-049. A seek to a
key at or before the end of the current leaf no longer moves the cursor.
The checks run once per leaf.

Tests: cursor through a cycle, a seek target past a stale leaf, an empty
leaf below the root (unit); every internal node of a checkpointed file
redirected to itself fails or answers within 60 s (integration). The
fuzz input is added as a seed.

Refs #597

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014v6aUco55yj3UScjL28mUP
The guard skipped inputs whose patterns join to more than 50,000 rows but
counted only the rows after the last pattern. A not-join body still runs
on the executor pattern by pattern in text order, so the nightly's
120-pattern body built hundreds of thousands of partial bindings before
its last pattern matched nothing, and timed out after 60 s. That is the
known #575 limit (streaming engine, #546 for negation), not a new bug.

The guard now counts the bindings after every pattern. On the v3 corpus
(4,783 inputs) coverage drops from 9,719 to 9,712 edges. The input is
added as a seed.

Refs #597

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014v6aUco55yj3UScjL28mUP
adityamukho and others added 3 commits October 15, 2026 09:02
On a damaged file the engine and the executor read different pages: for
[?e ?a ?v] the executor builds whole facts and looks up each tx timestamp
in the DICT, which the engine never reads. With the cycle on that DICT
path the executor fails with INT-049 while the engine answers, and the
oracle panics. The test still runs in the normal suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014v6aUco55yj3UScjL28mUP
@adityamukho
adityamukho merged commit e78021f into v3 Oct 15, 2026
24 checks passed
@adityamukho
adityamukho deleted the fix/597-fuzz branch October 15, 2026 04:00
@codecov
codecov Bot commented Oct 15, 2026
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/storage/btree.rs 77.77% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

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