Repository navigation
merge: main into v3 (v2.0.1, #323, #381, #315, #389, #297) - #412
Merged
Merged
Conversation
Main must stay releasable as v2.x so v2.1–v2.3 (and #370's unreleased fix) can ship without a forced one-way format migration. The v8 work (#371, #287) is preserved unchanged on the long-lived `v3` branch (at b8a9762), which will merge main regularly and becomes main when v3.0.0 is cut. This reverts commit b8a9762. Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Patch release on the v2.x line; file format unchanged (v7), no API changes. Ships #372: the index rebuild on open now uses each fact's real on-disk location, so entity-bound lookups no longer come back empty after a crash-triggered rebuild of a multi-checkpoint file. Doc sync: CHANGELOG (v2.0.1 entry + known issues #371/#287, #373/#374), CLAUDE.md, README.md, ROADMAP.md (v2.x on main, v3.0.0 on the `v3` branch), docs/TEST_COVERAGE.md (1154 tests). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy
…mitigation) (#380) * docs: design spec for point-query history scan mitigation (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * docs: implementation plan for #323; spec: drop dedup, threshold collapse Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: add in-crate FxHash hasher for hot-path maps (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: borrow keys and use FxHash in net_asserted_facts (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: add (entity, attribute) EAVT range lookup (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: narrow bound-entity fetch to queried attributes, drop redundant dedup (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: rotate FxHasher output so hashbrown's low-bit bucket index is mixed (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * bench: point_query_chain_depth (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * docs: changelog, benchmarks and test counts for #323; revise spec acceptance to measured Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * fix: bound (entity, attribute) EAVT scan exactly; pin same-tx multi-window fix (#323) End the committed range at attribute + NUL (exact successor) instead of incrementing the last byte, which returned None for attributes ending in 0x7F or a 0xBF UTF-8 byte and left the scan unbounded to the end of the index. Also stops resolving prefix siblings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: keep std's keyed hasher in net_asserted_facts; drop FxHash (#323) Fact values are often untrusted text (agent memory); a fixed-seed fast hash would let crafted colliding values make queries quadratic. The borrowed-key / encode-once rewrite stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * docs: final #323 benchmark numbers (std hasher, AC) and test counts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…382) get_facts_by_attribute ended its AEVT range at next_string_prefix(attr), which increments the last byte. For attributes ending in 0x7F or 0xBF (e.g. `:丿`, `:ÿ`) that is not valid UTF-8, so the committed scan ran to the end of the AEVT index. It also covered prefix siblings (`:ab` for `:a`). End the range at `attribute + "\0"`, the exact successor, as #380 does for the entity path. Remove the now-unused next_string_prefix. Sparse `:丿` scan (100 of 40.1k facts, checkpointed): 45.4 ms -> 251 µs. Closes #381 Refs #323 Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Shared runners make single-night Bencher alerts mostly noise. From Jul 1 to Sep 25, 31 of 87 nightly runs raised alerts, and none were real regressions (see #301). The report step now: - saves the Bencher output and reads the alerted benchmark names - reads the same suite's alerts from the previous completed run - reports only benchmarks that alerted on both runs - comments on an open regression issue instead of skipping it, so an old open issue no longer hides new alerts - still reports at once when Bencher fails without listing alerts Adds the actions: read permission to read the previous run's job logs. Claude-Session: https://claude.ai/code/session_019dotPmboGEcQoQWSngbceY Co-authored-by: Claude <noreply@anthropic.com>
…every index (#315) (#402) * docs: design spec for incremental checkpoint index rebuild (#315) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * docs: implementation plan for incremental checkpoint (#315) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * bench: add checkpoint/after_1_fact group (#315) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * bench: run one dirty fact per checkpoint iteration (#315) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * refactor(btree_v6): extract leaf encoding, fill rule, internal-level build (#315) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * feat(btree_v6): incremental rebuild that copies untouched leaves (#315) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * perf: checkpoint copies untouched index leaves and skips re-hashing fact pages (#315) save() snapshots each index's raw leaf pages and rebuilds via rebuild_btree_incremental: leaves no pending entry touches are copied verbatim, touched leaves split balanced so repeated checkpoints do not fragment the tree. The CRC32 state over unchanged fact pages is cached so each save hashes only new fact pages and the index region. File format and stored checksum are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * docs: checkpoint cost and durability semantics; #315 benchmarks and doc sync Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy * fix: reject cyclic leaf chains and damaged leaves before copying them (#315) Untouched leaves are now copied verbatim and sealed under a fresh file checksum, so collect_leaf_pages bounds-checks each leaf's slot directory and caps the chain at the file's page count instead of looping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#403) v2.3.0 becomes v2.0.2: the merged #323/#381/#315 mitigations plus #389, #393, #395, #397, #399, #400. The new production-readiness issues (#383 tracker) go into v3.0.0 with format v8 and #371. Features from v2.1.0/v2.2.0 and the old v3.0.0 scope move to a new v3.1.0. Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…/WAL and deleting the WAL (#389) (#404) On POSIX a created or deleted file survives a power loss only once its parent directory is fsynced. Minigraf fsynced file contents but never a directory, so a power loss could lose a freshly created .graph or <db>.wal, or resurrect a WAL that checkpoint() had deleted. Add storage::dir_sync::sync_parent_dir (open parent dir + sync_all on Unix; no-op elsewhere) and call it: - in FileBackend::open_with after writing the header of a new/empty file - in WalWriter::open_or_create after creating the WAL, and after re-initializing a headerless WAL left by a crash - in WalWriter::delete_file after a successful remove A test-only thread-local log records each sync and whether the child existed at that moment, so unit tests check both that the sync happens and that it follows the create/remove. Resurrected-WAL replay idempotence was already covered by tests/edge_cases_test.rs::test_stale_wal_after_checkpoint_is_idempotent. Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…408) - ERROR_REFERENCE: WAL-001/WAL-002/STG-011 said deleting the .wal loses no data, or that a database can be rebuilt from the WAL alone. Both are wrong. Replace with safe steps. Fix ~50 wiki links that pointed at a local .wiki path. - ROADMAP/CLAUDE.md: match the v2.0.2, v3.0.0 and v3.1.0 milestones. Document error.rs, magic_sets.rs, fault_inject.rs and src/browser/. - README: state MSRV 1.89, show the #371 known issue, fix Maven coordinates (io.github.project-minigraf), Android on Maven Central, and point binding downloads to the binding repos. - SECURITY.md: 2.x is the supported line. Refs #383, #397, #399 Claude-Session: https://claude.ai/code/session_01BBxgR4mnF8VMVGa42idcxx Co-authored-by: Claude <noreply@anthropic.com>
…#297) (#409) A recursive rule whose intermediate variable is bound by a call to another user rule, e.g. (chain ?x ?y) :- (link ?x ?mid) (chain ?mid ?y), failed with "Unbound variable in rule head: ?mid" (INT-022) when queried with a literal first argument such as (chain :a ?y). build_propagation_rules built the magic rule for the recursive call from the guard plus only the non-RuleInvocation clauses preceding it, so it emitted (__magic_chain_bf ?mid) :- (__magic_chain_bf ?x) with ?mid unbound. #300 fixed only the case where a fact pattern binds the intermediate. Keep every preceding clause, including rule invocations, as standard magic-sets rewriting does. All rewritten rules are positive, so the added dependency on the (guarded) called predicate is safe. Adds integration tests for the reopened repro and a left-recursive variant, and a unit test pinning the propagation-rule body. Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The v8 revert on main (#378, 15aaf22) was recorded with `-s ours` in the previous merge commit, so v3 keeps file format v8. Conflict resolutions: - storage.rs: #381's exact `attribute + "\0"` AEVT bound and #323's (entity, attribute) EAVT range use the v8 key constructors; adds `EavtKey::entity_attribute_start` since v8 keys carry value_bytes/asserted. - executor.rs: take main's attribute-narrowed selective fetch without dedup (#323). v8's net_asserted_facts keeps one winner per (entity, attribute, value, valid window), so duplicate records are harmless and the #371 widened dedup key is no longer needed. - persistent_facts.rs: keep #315's cached fact-page CRC prefix; drop the v7-era fact-only checksum fallback, which v3 already removed. save()'s leaf copy only sees v8 leaves: a v7 open rebuilds and writes a v8 header before any save. - Docs: ROADMAP takes main's milestone layout; CHANGELOG keeps v3's Unreleased section and adds main's v2.0.2 entries and v2.0.1 below it; test counts updated (1198 passing, 8 ignored). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings everything on
mainsince the v8 split intov3. Merge with a merge commit, not squash or rebase, so git records main as merged and future main→v3 merges stay small.Keeping v8
maincontains15aaf22(#378), which reverts format v8. It is recorded as merged withgit merge -s ours(commite884c42), so v3 keeps its v8 code. The rest of main is merged normally incf23bfc.Conflict resolutions
src/graph/storage.rs: perf: attribute scan is unbounded to end of AEVT index for attributes ending in a 0xBF/0x7F byte #381's exactattribute + "\0"AEVT bound and perf: point query on a bound entity scans the entity's whole version history — O(retract/reassert count), not O(live facts) #323's(entity, attribute)EAVT range now use the v8 key constructors. AddsEavtKey::entity_attribute_start, because v8 keys also carryvalue_bytesandasserted.src/query/datalog/executor.rs: takes main's attribute-narrowed selective fetch, which does no dedup (perf: point query on a bound entity scans the entity's whole version history — O(retract/reassert count), not O(live facts) #323). v8'snet_asserted_factskeeps one winner per (entity, attribute, value, valid window), so duplicate records are harmless, and v3's widened bug: same-transaction multi-valued facts read back as ONE value, differing between EAVT and AEVT; a batched retract can leave retracted values visible #371 dedup key is no longer needed.src/storage/persistent_facts.rs: keeps perf: checkpoint() is O(graph size) and flat in dirty bytes — it rebuilds all four covering indexes every call #315's cached fact-page CRC prefix and drops the v7-era "fact pages only" checksum fallback, which v3 had already removed. perf: checkpoint() is O(graph size) and flat in dirty bytes — it rebuilds all four covering indexes every call #315'ssave()copies untouched index leaves verbatim; that only ever sees v8 leaves, because opening a v7 file rebuilds the indexes and writes a v8 header before any save.v3. CHANGELOG keeps v3's[Unreleased]section, then main's v2.0.2 entries and v2.0.1. Test counts updated.Verification
cargo fmt --checkandcargo clippy -- -D warningsare clean.cargo test: 1198 passed, 0 failed, 8 ignored.🤖 Generated with Claude Code
https://claude.ai/code/session_01BedF9ZmqVZLkvDwn91fuKy