8000 Releases · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

Releases

v2.0.11 — 2026-10-26

Choose a tag to compare

@github-actions github-actions released this 26 Oct 04:15

Release Notes

Patch release on the v2.x line under the support policy: two query data-integrity fixes, for expressions that compared a keyword and a ref to its entity differently from joins (#744), and float-literal rule calls whose -0.0 rows varied between runs (#750), plus checkpoint() deadlocking inside a WriteTransaction (#734). File format is unchanged (v7). No API changes. Upgrading is recommended for every v2.x user who compares entity refs in expressions, passes float constants to rules, or uses WriteTransaction.

Fixed

  • A rule call with the float 0.0 returns the row of -0.0 in every run (#750), backported from v3.0.0 (#545). 0.0 == -0.0, as patterns and predicates compare floats, but Value's hash gave the two different hashes, so a join through a hash table paired them only when the process's random hash seed happened to: [:find ?e :where (xval ?e 0.0)] for a rule (xval ?e ?v) returned the entity whose value is -0.0 in about one run in twelve and left it out otherwise, with no error. -0.0 and 0.0 now hash alike, so the rule call returns both rows, as [?e :m/x 0.0] does. Every other float hashes as before. Nothing stored was wrong.
  • An expression's = and != equate a keyword with a ref to the entity it names (#744), backported from v3.0.0. Patterns and joins treat the keyword :bob as equal to a ref to the entity :bob names, but [(= ?g :bob)] compared the two values as they are, so with ?g bound to that ref it kept no row and [(!= ?g :bob)] kept the row: the same question answered differently by how the query was written, with no error. = and != now compare as joins do, in queries, rule bodies, not clauses and binding expressions, against a keyword constant, a keyword value or a bound Val(Ref). A keyword still equals only the same keyword, and <, >, <= and >= on a keyword or a ref are still a type mismatch (no row).
  • checkpoint inside a WriteTransaction returns INT-001 instead of deadlocking (#734), backported from v3.0.0. Called on a thread that holds a WriteTransaction, checkpoint waited on the write lock that the transaction already held, so the thread hung. It now returns INT-001, as execute and begin_write already did, and the transaction stays usable. prepare and PreparedQuery::execute make the same check: they could not deadlock, but they ran inside the transaction while execute refused. Present since WriteTransaction was added.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.11

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.10 — 2026-10-23

Choose a tag to compare

@github-actions github-actions released this 23 Oct 07:14

Release Notes

Patch release on the v2.x line under the support policy: two data-integrity fixes, for WAL entries of more than 1,000,000 facts that replay dropped along with every later transaction (#686) and for float constants that matched any float within f64::EPSILON (#706), plus prepared queries that ignored the database's rule limits (#679). File format is unchanged (v7). No API changes. Upgrading is recommended for every v2.x user who commits transactions of more than 1,000,000 facts or matches float constants in patterns.

Fixed

  • A float constant in a pattern matches equal floats only (#706), backported from v3.0.0. A float written in a pattern's value position, or bound to a $slot of a prepared query, matched every stored float within an absolute f64::EPSILON (about 2.2e-16), so [?e :m/x 0.0] returned an entity whose :m/x is 1e-17, while [?e :m/x ?v] [(= ?v 0.0)] did not. Above 1.0 the epsilon is smaller than the gap between adjacent floats, so only floats smaller than about 2.2e-16 in magnitude were affected: every one of them matched 0.0, and 1e-20 matched 5e-20. Float constants now compare by IEEE ==, as joins, predicates and the indexes do: 0.0 matches -0.0 and nothing else changes. The fix covers patterns in queries, rule bodies and not / or bodies. Nothing stored was wrong.
  • WAL entries of more than 1,000,000 facts replay (#686), backported from v3.0.0. A transact, retract or WriteTransaction commit of more than 1,000,000 facts wrote a WAL entry that replay took as damage: replay stopped there, and the next open cut the WAL off at that entry, so after a crash, kill or close without a checkpoint that transaction and every later one in the WAL were lost, though each had reported success. Replay now takes an entry's fact count as damage only when the bytes after it cannot hold that many facts. A checkpoint before the crash kept the facts. Present since v0.5.0.
  • Prepared queries use the database's rule limits (#679), backported from v3.0.0. PreparedQuery::execute ran a rule query with the default max_derived_facts and max_results of 1,000,000 whatever OpenOptions set, so a lower limit set to stop a runaway recursive rule did not apply to prepared queries, and a higher one set for a large derivation failed with INT-020 where execute of the same query succeeded. It now uses the database's limits, as execute does. :max-derived-facts and :max-results written in the query text were always applied. Present since v0.19.0.

Tests

  • Kani proofs of the value encoding (#627), backported from v3.0.0. src/storage/index/verification.rs holds 5 Kani harnesses, compiled only under cfg(kani). They prove for every input that encode_value, the value part of the v7 index keys, gives each of Null, Boolean, Integer, Float and Ref its own type byte and keeps each one's order, and that every NaN encodes to the same bytes. .github/workflows/kani.yml runs them on pull requests that touch the encoder and nightly on main and v3. Strings, keywords, decoders, fact pages and the WAL are not covered: the model checker does not finish on them. Test-only; Cargo.toml declares the kani cfg so normal builds do not warn.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.10

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.9 — 2026-10-21

Choose a tag to compare

@github-actions github-actions released this 21 Oct 07:07

Release Notes

Patch release on the v2.x line under the support policy: two data-integrity fixes, for a pseudo-attribute in a rule body that read the wrong valid time and tx metadata (#661) and for a fact of more than four elements whose extra elements were silently dropped (#649), plus error codes in BrowserDb rejections (#673). File format is unchanged (v7). No API changes; a fact of more than four elements, which used to be stored truncated, now fails with PRS-108. Upgrading is recommended for every v2.x user who reads :db/valid-from, :db/valid-to, :db/tx-count or :db/tx-id in rules.

This is also the first release whose language packages are installed and used, the way a user installs them, before they are published and again after (binding consumer tests). That check found and fixed packaging bugs in the 2.0.8 packages: the npm minigraf package did not load, the Android AAR had no classes, the Swift package did not compile, the C macOS library pointed at the CI build path and the Windows archive had no import library, the Java jar did not load on Alpine or Windows arm64, and the C and WASM packages shipped no license files. Use 2.0.9 or later of every binding.

Fixed

  • BrowserDb errors carry their error code (#673). The browser API (@minigraf/browser) rejected with the error's top-level message only, such as Unknown command: nosuchcommand, while every other API reports [PRS-001] Unknown command: nosuchcommand. Every BrowserDb rejection now goes through MinigrafError, so it starts with its code. Host errors that have no code yet (no IndexedDB, an import blob whose length is not a multiple of the page size) are unchanged.
  • A pseudo-attribute in a rule body reads the stored fact (#661). In a rule without not, not-join, or or or-join, :db/valid-from, :db/valid-to, :db/tx-count and :db/tx-id read the rule evaluator's working copy of the fact: valid-from was the time the query ran, valid-to forever, and the tx-count and tx-id those of the copy. With [:a :edge :b] valid from 2020-01-01 to 2021-01-01, (rule [(vf ?x ?t) [?x :edge ?y] [?x :db/valid-from ?t]]) returned the query's time for :a; it now returns 2020-01-01, as the same pattern in a query body does. Stored data was never affected. Present since v0.15.0.
  • A fact with more than four elements is rejected (#649). transact read a fact's 4th element as its optional valid-time map and ignored everything after it, so [[:alice :person/age 1 {} 99 :junk]] stored [:alice :person/age 1] and dropped the rest without an error; two facts run together by a misplaced bracket lost one. A fact of more than four elements now fails with the new PRS-108, numbered as in v3.0.0, and the transaction writes nothing. A fact of five or more elements whose 4th is not a map now gets PRS-108 instead of PRS-047. Present since v0.4.0.

Tests

  • Parser vs grammar verifier (#643), backported from v3.0.0. src/query/datalog/parser/verifier.rs checks, for every generated input, that the parser rejects what tests/grammar/grammar.pest rejects, and accepts what it accepts unless the error code is semantic (a listed code with a witness pest accepts). It runs every string of 1 to 3 characters over 102 characters (ASCII, \t \n \r, four non-ASCII stand-ins) through parse_edn, and every bracket-balanced sequence of up to MINIGRAF_GRAMMAR_BOUND (default 3) of 32 tokens in 15 command templates through parse_datalog_command, also with no separators up to 3 tokens. Deterministic; about 40 s by default in a debug build. On v2.x INT-019 still mixes syntax and semantic parse errors (v3.0.0 splits it into PRS-083–PRS-107), so it counts as semantic here, and a syntax error coded INT-019 that pest accepts is not caught (v3.0.0 checks that too). The drift it found is the same as on v3 and is listed in KNOWN_DRIFT by cause (comments and non-ASCII letters #650, since documented: see below; empty sections, ignored keywords, rule and call heads, a lone : or -, unterminated strings #651; a fact of more than four elements, #649, is fixed above).
  • The grammar documents comments and non-ASCII identifiers (#650), backported from v3.0.0. The parser has always accepted both; tests/grammar/grammar.pest now says so. A ; starts a comment that runs to the end of the line and is skipped like whitespace. Symbols, variables, keywords and bind slots take Unicode letters and numerals (:person/名前, ?ñ), as Rust's char::is_alphabetic and char::is_numeric define them; a symbol starts with a letter or _, and - followed by a numeral starts a number, not a symbol. Nothing the parser accepts or rejects changes. grammar.pest now says what the parser means: a fact is exactly [e a v] plus an optional valid-time map and a retracted fact exactly [e a v]; not, not-join, or, or-join (and and inside an or) are never rule names; literal keywords end at a word boundary (:find?x is one keyword). A nightly workflow, grammar-verifier.yml, runs it at bound 4 on main and on v3, and opens or comments on an issue when it fails.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.9

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.8 — 2026-10-20

Choose a tag to compare

@github-actions github-actions released this 20 Oct 03:38

Release Notes

Patch release on the v2.x line under the support policy: seven data-integrity fixes for queries that silently returned too few or too many rows — :db/valid-at in not-join bodies (#615) and rule definitions (#617), or / or-join branches (#620, #625, #637), and rules that call or recurse through a rule with not, not-join, or or or-join (#628, #633). File format is unchanged (v7). No API changes. Stored data was never affected. Upgrading is recommended for every v2.x user who uses or, not-join, :db/valid-at or rules with negation or disjunction.

Fixed

  • A :db/valid-at pattern in a not-join body binds the query's valid time on every path (#615). When the body also held an expression, sat in an or branch, or ran in a query that invokes a rule, the body bound nil, so (not-join [?e] [?e :db/valid-at ?t] [(> ?t 0)]) never matched and excluded nothing; without the expression the same body excluded every entity. It now binds the query's :valid-at value (or the current time) as the other paths do. Present since v0.13.1.
  • A :db/valid-at pattern in a rule definition binds the query's valid time (#617). The rule evaluators never received it, so in a rule body the pattern bound nil and the query failed with INT-022; in a rule's not / not-join body it bound nil, so the negation never matched on it; and in a rule's or branch it bound the current time instead of the query's :valid-at. Rule bodies, their negation bodies and or branches now bind the query's :valid-at value, the current time without one, and nil under :any-valid-time, as patterns outside rules do.
  • An expression reading a variable only an or binds keeps its rows (#625). The planner places expressions among the patterns and nots, and or / or-join run after them, so an expression such as [(> ?x 1)] in [?e :n _] (or [?e :n ?x] [?e :ok ?x]) [(> ?x 1)] ran before ?x was bound and dropped every row, in queries and in rule bodies. Such an expression now runs after the ors, before the nots that wait for them, as those nots already did.
  • A rule calling a rule with not, not-join, or or or-join gets all its rows (#628). Stratification put a rule in the same stratum as the rules it calls, and within a stratum the rules without those clauses run first, so (rule [(st ?m ?s) [?m :msg/actor ?a] (cur ?a ?s)]) ran before any cur row existed when cur had a not-join, and returned nothing; a caller that had a not or or of its own ran before or after its callee depending on the query. A rule now sits in a stratum above any rule with such a clause it calls, unless the two call each other.
  • Recursion through a rule with not, not-join, or or or-join returns all its rows (#633). Within a stratum such a rule ran once, after the rules without those clauses had reached their fixpoint, so its rows never fed back into the recursion: (rule [(reach ?x ?y) [?x :e ?z] (reach ?z ?y) (not [?y :blocked true])]) stopped after one step and returned 7 of 10 pairs on a four-edge chain, with no error. A stratum whose rules call such a rule in it now repeats until that rule derives nothing new, bounded by the rule limits (INT-020), and the rule no longer adds a row it already derived. max_results now counts every fact the rules derive in one evaluation, across strata and repeated passes, as documented; it used to count each stratum's rules without those clauses separately and not the rules with them at all.
  • An or / or-join branch meets a keyword value as the entity it names (#637). Unless a branch held a not or an expression reading an outer variable, the executor ran each branch alone and joined its rows to the outer rows by exact value, so a keyword never met the ref of the entity it names: with [:alice :friend :bob] [:bob :name "B"], [:find ?e ?n :where [?e :friend ?f] (or [?f :name ?n] [?f :alias ?n])] returned no rows instead of :alice "B", with no error, in queries and in rule bodies. The join now compares them as a join between patterns and a not-join already did.
  • An or / or-join branch whose expression reads an outer variable matches (#620). A branch such as [(> ?v 1)] in [?e :n ?v] (or [(> ?v 1)] [(< ?v 0)]) was evaluated on its own from an empty binding and joined back afterwards, so its expression saw ?v unbound and the branch matched nothing: the or silently dropped rows it should keep, in queries and in rule bodies. A branch whose expression reads a variable the branch does not bind itself is now seeded with each incoming row (an or-join branch with the row's join variables).

Tests

  • tests/not_join_test.rs checks :db/valid-at in not-join bodies on the per-row path (#615), rules calling a rule with not, not-join or or (#628), and recursion through such a rule (#633); tests/recursive_rules_test.rs checks :db/valid-at in rule definitions, their negation bodies and or branches (#617); tests/disjunction_test.rs checks or branches whose expressions read an outer variable (#620), expressions reading a variable only an or binds (#625), and keyword values meeting the entity they name (#637). Stratification, evaluator and optimizer unit tests cover the new strata, the repeated passes and their limits, and expression placement.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.8

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.7 — 2026-10-16

Choose a tag to compare

@github-actions github-actions released this 16 Oct 08:13

Release Notes

Patch release on the v2.x line under the support policy: one data-integrity fix for prepared queries (#603) and two query fixes for rules with literal arguments (#608) and or clauses that share no variable with the earlier clauses (#610). File format is unchanged (v7). No API changes. Upgrading is recommended for every v2.x user who uses prepared queries with a literal :as-of or :valid-at, rule calls with a literal argument, or or.

Fixed

  • Rule queries with a literal argument return every row (#608). Magic-sets rewriting restricts a rule to the facts its callers demand, but it took each rule's bound arguments from the first call it saw and assumed every other call matched. A call that did not — the same rule called with free arguments elsewhere in the query, a call inside or or not, a call from a rule the query calls with free arguments, or a rule-body call whose argument nothing bound yet — either failed with INT-022 ((chain ?x :c) with (chain ?x ?y) :- (link ?x ?m) (chain ?m ?y)) or silently returned too few rows ((link ?x :c) (link ?p ?q) returned only the :c edges for ?p ?q). A query with both arguments literal next to another rule call also lost its rows. The demand on each rule is now the one every call site supplies, and a rule some call site reads in full is evaluated in full. Present since v1.2.0; also fixed on v3.
  • An or that shares no variable with the earlier clauses returns its cross product (#610). Such an or — [?x :edge ?z] (or [?p :edge :b] [?p :edge :c]) — returned no rows when no branch had not or not-join, because the incoming rows were replaced by the branch results. Each incoming row now joins every branch row, in queries and rule bodies. or-join still refuses a join variable no earlier clause binds (INT-018): it filters, it does not bind. Also fixed on v3.
  • A prepared query keeps a literal :as-of, :valid-at or :any-valid-time (#603). Binding a prepared query's slots dropped any :as-of or :valid-at that was not itself a slot, so PreparedQuery::execute ran at the current transaction time and valid now: (query [:find ?v :as-of 1 :where [:a :n ?v]]) prepared returned the current rows, not those at transaction 1, and a prepared per-fact pseudo-attribute query with :any-valid-time failed with INT-026. Slot forms (:as-of $tx, :valid-at $t) were not affected. Present since 2026-04-04 (v1.x and every v2.x release).

Tests

  • tests/prepared_statements_test.rs checks that a prepared query with a literal :as-of, :valid-at or :any-valid-time returns the same rows as the unprepared query (#603); tests/magic_sets_test.rs checks rule calls with a literal argument where some call site cannot carry the demand (#608); tests/disjunction_test.rs checks the cross product of an or that shares no variable with the earlier clauses, in queries and rule bodies (#610).

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.7

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.6 — 2026-10-14

Choose a tag to compare

@github-actions github-actions released this 14 Oct 02:46

Release Notes

Patch release on the v2.x line under the support policy: three data-integrity fixes for queries that run on another thread while a transaction commits or a checkpoint runs (#554, #555, #558). File format is unchanged (v7). No API changes. Stored data was never affected. Upgrading is recommended for every v2.x user who queries and writes from different threads.

Fixed

  • A query on another thread sees each transaction whole (#554). A commit wrote its WAL entry, then added the transaction's facts to the in-memory store one fact at a time, each under its own lock, so a query running on another thread could return part of the transaction. execute("(transact ...)"), execute("(retract ...)") and WriteTransaction::commit all did this. They now add the whole transaction under one lock, as does WAL replay. Stored data was never affected. Affected WriteTransaction since v0.5.0 and execute since v0.6.0. Also fixed in v3.0.0.
  • All patterns of one query read the same state of the database (#555). A query whose patterns name entities or attributes read each of them with its own lookup, each under its own lock, so a transaction that committed between two lookups was seen by some patterns and not others, and the query could return rows that match no state of the database. The lookups now run under one lock. Stored data was never affected. Affected every release since v1.1.0. Also fixed in v3.0.0.
  • A query during a checkpoint no longer misses committed facts (#558). A checkpoint wrote its new fact pages over the index pages that queries on other threads were still reading, then installed its new readers and cleared the pending facts in three separately locked steps. A query during a checkpoint, including the automatic one every wal_checkpoint_threshold WAL entries, could miss facts committed before it started or see part of a transaction. Queries now wait for a checkpoint to finish: it holds the store's write lock from its first page write until the new state is published. Stored data was never affected. Affected every release since v0.9.0; v3.0.0's copy-on-write checkpoints are not affected.

Tests

  • tests/snapshot_atomicity_test.rs races a writer against a reader: a query must see each transaction whole, whether written with execute or a WriteTransaction (#554), and must read one state for all its patterns (#555), in memory and on file, and must not miss committed facts while a checkpoint runs (#558).

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.6

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.5 — 2026-10-11

Choose a tag to compare

@github-actions github-actions released this 11 Oct 08:17

Release Notes

Patch release on the v2.x line under the support policy: two data-integrity fixes (#506, #513) and three bug fixes backported from v3.0.0 (#528, #457, #476), plus a weekly soak test and working coverage gates. File format is unchanged (v7). Two new error codes, STG-045 and WAL-007, numbered as in v3.0.0; no other API changes. Upgrading is recommended for every v2.x user.

Fixed

  • A file shorter than one page is refused at open with STG-045 unless it is a torn first write (#506). Open treated any file under 4 KiB as a new database and wrote a new header over it, so a database cut below one page by an interrupted copy, or an unrelated file at the path, was silently replaced. Such a file now opens as a new database only when its bytes are zeros or a prefix of the header a new database writes (what a crash during creation can leave); anything else is STG-045 and the file is not modified. A zero-length file is still a new database. New code STG-045, numbered as in v3.0.0, which also fixes this.
  • A commit made after a torn WAL entry is no longer lost on recovery (#513). Recovery replays the WAL up to its first partial or damaged entry, but the writer appended after the end of the file. After a crash during an append, the next session's commits went in after the partial entry, so a second crash before any checkpoint lost them, although each had been fsynced and reported as committed. A failed append (short write, full disk, I/O error) did the same to the next commit through that handle. Opening a database for writing now cuts the WAL back to the end of its last valid entry, and syncs it, before anything is appended. After a failed append, every later write through that handle fails with the new WAL-007 until the database is reopened or a checkpoint() succeeds. The transaction whose append failed is not applied; after a reopen it is there in full or not at all. Affected every release since v0.5.0. Also fixed in v3.0.0.
  • Rule queries no longer fail with INT-020 on databases with 1,000,000 or more live facts (#528). The recursive evaluator counted the database's own facts against max_results, so once a database held that many live facts, every rule query failed at its first derived fact, whatever the size of its answer. max_results now counts only the facts the rules derive, so raising it with OpenOptions::max_results or :max-results is no longer needed. Affected every release since v0.18.0. Also fixed in v3.0.0.
  • checkpoint() removes a WAL whose entries are all already in the file (#457). When it had no facts to flush, it returned early and left the WAL until a later write was checkpointed. That WAL is left by a crash between a checkpoint's commit and its WAL delete, or by a failed delete (WAL-006). Its contents were always skipped on replay, so no data was affected; the sidecar file just stayed. Also fixed in v3.0.0.
  • Closing a handle opened with wal_checkpoint_threshold(usize::MAX) no longer writes its pending facts to the .graph file (#476). The sentinel was documented to suppress the close-time checkpoint, and Minigraf's close did skip it, but the storage layer still saved on drop, leaving a WAL whose entries were all checkpointed. The file now stays as the last explicit checkpoint() left it, and the WAL keeps every pending fact for the next open to replay, so bulk loaders and benchmarks see the pending WAL they ask for. No data is lost either way. Also fixed in v3.0.0.

Tests

  • Weekly soak test at scale (#392). tests/soak_test.rs grows a file-backed database to 10M live facts in batches of 1–2,000 facts, then churns it for the rest of a 5-hour budget at 50 transactions a second (retracts, re-asserts, long histories on 32 hot entities), with a checkpoint every 20–200 transactions and a reopen every 2–10 minutes. Samples are checked against a reference through EAVT, entity and attribute, AVET, AEVT, :as-of and the transaction counter, with a full scan in a child process at 2M facts. RSS after a reopen is bounded relative to the fact versions held. Metrics go to a JSON-lines file that .github/workflows/soak.yml uploads weekly, alternating main (Saturday) and v3 (Sunday). On v2.x no transaction writes two values of one entity and attribute, because of the known issue #371; the v3 workload fails here. It also checkpoints every 100–1,000 transactions and samples :as-of less, since both cost time in the database size on v2.x. soak_short runs the driver at 20K facts in the normal suite.
  • Coverage gates that gate (#396). The per-module gate in coverage-gates.yml had never checked anything: the instrumented test run failed (the lock tests could not find pid_ns_helper), the report it grepped was empty, and every module was skipped with a warning, so the job passed. It also read cargo llvm-cov --text, which has no per-file percentages. The job now runs the whole suite with branch coverage on a pinned nightly, fails if any test fails, and checks each file's line and branch percentage against coverage-thresholds.toml with scripts/coverage_gates.py; a file missing from the report, or without branch data, fails. The floors are the measured values less one point, for the WAL, db.rs, graph/storage.rs, the storage files (except the v5 B+tree kept for migration) and the evaluator, executor and stratification. New tests check that WAL replay stops at an entry whose first fact is over 10 MB (the old test changed bytes inside the fact and never reached the limit) or that claims more than 1M facts, that a WAL writer cannot be opened in a missing directory, and that two threads missing the same page in the page cache get the one cached copy (before, only a lucky race reached that path).

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), save() is not crash-atomic (#374), and a .wal from a later checkpoint is replayed onto an older .graph (#535); all are fixed in v3.0.0

Download minigraf 2.0.5

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.4 — 2026-10-08

Choose a tag to compare

@github-actions github-actions released this 08 Oct 08:51

Release Notes

Patch release on the v2.x line under the support policy: one BrowserDb durability hardening fix, plus new compatibility and crash tests. File format is unchanged (v7). No API changes. Native and WASI builds behave exactly as in v2.0.3.

Fixed

  • BrowserDb no longer drops a dirty page it cannot read from the IndexedDB flush (#470). checkpoint(), importGraph() and every write collected dirty pages with read_page_raw(id).ok(), so a page that failed to read was left out of the flush while the call still reported success. The read error is now returned. No current code path leaves a dirty id without a page, so this guards against a future bug rather than fixing observed data loss. Affected releases: v0.20.0 through v2.0.3, BrowserDb only. The browser unit tests compile again on wasm32 (a duplicate page_cache_capacity and tempfile-based dir_sync tests).

Tests

  • Golden-file compatibility corpus (#391). tests/golden/ holds v7 files written by minigraf 2.0.3, each with a JSON manifest of its CRC32, tx_count and expected query results. They cover one checkpoint, several checkpoints, indexes rebuilt on open, a WAL left by a crash, a WAL whose entries were already checkpointed, and same-transaction multi-values (#371). tests/golden_corpus_test.rs opens a copy of each file and checks its manifest after open, after a checkpoint and reopen, and after one more write. Golden files are never regenerated, only added; a new golden-corpus job in policy.yml fails a pull request that changes one. The generator is a standalone crate pinned to minigraf = "=2.0.3" (tests/golden/gen/v7/).
  • SIGKILL crash test checks committed data (#384). tests/crash_kill_test.rs used to check only that the file reopened and one attribute query ran, so it missed #370. The child now runs a seeded workload (batches over several entities and attributes, batched retracts, re-assertions after a retract) and logs each transaction after execute returns; it checkpoints after every transaction, every third, never (WAL replay), automatically every 2 writes, or every third with SyncMode::Normal. After a SIGKILL at a random point, the reopened file must hold exactly the model after the last logged transaction or the one in flight, through a full scan, attribute (AEVT), attribute+value (AVET) and entity-bound (EAVT) queries and :as-of; again after a second reopen and after a checkpoint plus reopen. On v2.x the workload avoids the known issues #371 (two values of one attribute in one transaction) and #435 (asserting a triple that is already live returns one row per assertion); the v3 version covers both. Per-PR CI runs 5 rounds; the nightly crash-kill workflow runs 100 per OS. MINIGRAF_CRASH_KILL_SEED replays a failure.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287), a fact retracted and re-asserted in one WriteTransaction is lost at commit (#477), a later assertion does not replace an earlier valid-time window (#435), and save() is not crash-atomic (#374); all are fixed in v3.0.0. All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.4

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.3 — 2026-10-06

Choose a tag to compare

@github-actions github-actions released this 06 Oct 05:11
efac91f

Release Notes

Patch release on the v2.x line with two data-integrity fixes, under the support policy. File format is unchanged (v7). No API changes. Upgrading is recommended for every v2.x user.

Fixed

  • Queries returned wrong results with more than 65,535 uncheckpointed facts (#445). Each fact not yet checkpointed was indexed by its position in memory, stored as a 16-bit number that stopped counting at 65,535. Every later fact was indexed as that same position, so entity-bound queries ([:e :attr ?v]) and attribute scans ([?e :attr ?v]) silently lost or duplicated facts. This affected in-memory databases with more than 65,535 facts and file databases with more than 65,535 facts written since the last checkpoint. The stored data was always intact, and a checkpoint made file databases correct again. Positions are now 32-bit and checked. Affected releases: v0.7.0 through v2.0.2. The file format is unchanged.
  • Reopening next to an already-checkpointed WAL rewound the transaction counter (#447). The WAL normally disappears after a checkpoint. It survives one only if the process crashes between the checkpoint's commit and the WAL delete, if the delete fails (WAL-006), or with wal_checkpoint_threshold(usize::MAX). When such a WAL held only entries already in the file, replay skipped them all and then reset the transaction counter from the facts it had loaded, which were none, so it reset to 0. The next transactions reused tx_count values already in the file, :as-of answers mixed old and new transactions, and the next checkpoint made that history permanent. The counter now never moves backwards on replay. Affected releases: v0.5.0 through v2.0.2.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#371, #287); the fix needs file format v8 and ships in v3.0.0. save() is not crash-atomic (#374). All v2.x known issues, with affected versions and workarounds, are listed in the pinned issue #421.

Download minigraf 2.0.3

File Platform Checksum
minigraf-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
minigraf-x86_64-apple-darwin.tar.xz Intel macOS checksum
minigraf-x86_64-pc-windows-msvc.zip x64 Windows checksum
minigraf-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
minigraf-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

v2.0.2 — 2026-09-27

Choose a tag to compare

@github-actions github-actions released this 27 Sep 03:54

Release Notes

Final planned release on the v2.x line. File format is unchanged (v7). No breaking API changes; one new user-facing error code (API-010). v2.x gets data-integrity and security fixes for 12 months after v3.0.0 ships (support policy).

Performance

  • Bound-entity point queries no longer pay for other attributes' history (#323). [:e :attr ?v] now range-scans only (e, :attr) in the EAVT index instead of every record the entity has ever written. Reading a rarely changed attribute of a heavily rewritten entity no longer slows down as that entity's history grows: with 2,000 retract/reassert cycles on another attribute of the same entity, it drops from 4.58 ms to 19.0 µs. Reading the heavily rewritten attribute itself, and attribute scans ([?e :attr ?v]), are about 1.35–1.4× faster (4.64 ms → 3.36 ms), from cheaper net-assert grouping and removing a redundant dedup pass. The file format is unchanged.

  • Attribute scans are bounded to exactly the queried attribute (#381). [?e :attr ?v] computed the end of its AEVT range by incrementing the attribute's last byte. For attributes whose last byte is 0x7F or 0xBF — including about 1 in 64 non-ASCII characters, such as :丿 or :ÿ — the result was not valid UTF-8, so the scan ran to the end of the whole AEVT index and read every later fact before filtering. It also read facts for prefix siblings (:ab, :a/b when scanning :a). The range now ends at attribute + "\0", which covers exactly one attribute. Scanning 100 :丿 facts in a checkpointed file with 40,000 facts on neighbouring attributes drops from 45.4 ms to 251 µs. Results are unchanged.

  • Checkpoints no longer re-encode the whole index (#315). checkpoint() decoded and re-serialised every entry of all four covering indexes and re-read every page of the file for its checksum, so checkpointing after one new fact cost almost as much as after thousands. Index leaves that receive no new entries are now copied verbatim, only the leaves that do are decoded and re-split, and unchanged fact pages are no longer re-hashed. Leaves are still bounds-checked and the leaf chain is checked for cycles, so a damaged index fails the checkpoint instead of being copied forward. Checkpoint after one new fact drops from 246 ms to 26.3 ms on a 100k-fact file and from 23.9 ms to 4.0 ms at 10k. Cost still grows with graph size, because the index pages are copied on every checkpoint; the file format is unchanged. The checkpoint() and wal_checkpoint_threshold docs now state that a checkpoint is not a durability boundary: the WAL is already crash-durable.

Fixed

  • A power loss could lose a new database or WAL file, or bring back a deleted WAL (#389). On Linux and other POSIX systems, a created or deleted file survives a power loss only once its parent directory is fsynced; Minigraf fsynced file contents but never a directory. Creating the .graph file or the <db>.wal sidecar, and deleting the WAL after a checkpoint, now fsync the parent directory after the file operation. A resurrected WAL was already harmless, since replay skips entries at or below the last checkpointed transaction, but a lost .graph or WAL lost committed data. Windows needs no directory sync and is unchanged. Process kills were never affected, because they leave the OS page cache intact.
  • Recursive rules failed with a literal start when the recursion goes through another rule (#297). (query [:find ?y :where (chain :a ?y)]) over (chain ?x ?y) :- (link ?x ?mid) (chain ?mid ?y) failed with Unbound variable in rule head: ?mid (INT-022). The magic-sets rewrite, which runs when a rule argument is given as a literal, dropped earlier rule calls from the rules that pass bindings to the next call, so a variable bound only by such a call (here ?mid, bound by link) was never bound. #300 fixed the case where a fact pattern binds it. Those calls are now kept, and results match the same query without a literal start.
  • or-join failed with INT-031 when no rows reached it (#405). A valid query such as [?item :order-item/product ?p] (or-join [?item] ...) returned or-join variable ?item is not bound in the incoming scope on an empty database, or whenever the clauses before the or-join matched nothing, but worked once matching data existed. A safety check read the bound variables from the incoming rows, so with no rows every join variable looked unbound. or-join now returns an empty result when no rows reach it, like or and not-join already did. This applies to queries and rule bodies.
  • A query with $slot bind tokens run through execute() reported an internal error (#407). Such a query failed with [INT-025] internal: unsubstituted :valid-at bind slot reached the executor when it had a :valid-at slot, and with slots in other positions it could run and quietly return nothing. Minigraf::execute(), WriteTransaction::execute() and the REPL now reject it before execution with the new user error API-010, which names the slots and points to prepare(). Prepared queries are unchanged.
  • Bound-entity queries could drop rows when one WriteTransaction wrote the same attribute in several valid-time windows (#323). Both facts share a tx_count, and the selective lookup path de-duplicated on (entity, attribute, tx_count, asserted), so one window's value was silently lost, while the same query via a full scan returned both. That de-duplication has been removed, and bound-entity queries now always match a full scan.
  • The INT-054 negative-cycle error named its two predicates in a random order (#410). Registering (rule [(p ?x) (not (q ?x))]) and then (rule [(q ?x) (not (p ?x))]) reported predicate 'q' is involved in a negative cycle through 'p' on some runs and the reverse on others, because stratification walked the rule dependency graph in HashMap order. It now walks rules in sorted order, so the same rules always give the same message. Which rules are accepted or rejected is unchanged.

Tests

  • Crash at every point inside save() (#374, #390). A fault-injection test makes a checkpoint fail at each page write and each sync in turn, on a file already written by several checkpoints, then reopens what reached storage. At all 47 crash points the file reopens with either the old header and exactly the previously checkpointed facts (old last_checkpointed_tx_count, so WAL replay re-applies the in-flight transaction) or the new header and all facts, and every fact is found by both entity and attribute lookups. 45 of the 47 reopens take the index-rebuild path. This checks the v2.x behaviour described in the known-issues list (#421); it models a process kill, not a power loss.

CI

  • New Policy workflow (#395). An MSRV job runs cargo check --all-features and cargo test --lib on Rust 1.89, resolving dependencies to 1.89-compatible versions, and fails if rust-version in Cargo.toml drifts from the tested toolchain. cargo-semver-checks compares the public API with the latest release tag and blocks PRs to main; on major-version branches such as v3 it reports breaks without failing. cargo-deny (deny.toml) allows only permissive licenses compatible with MIT OR Apache-2.0, rejects duplicate crate versions and wildcard requirements, and requires every crate to come from crates.io.

Documentation

  • Unsafe WAL recovery advice removed from docs/ERROR_REFERENCE.md. The WAL-001, WAL-002 and STG-011 resolutions said a .wal file could be deleted with no data loss, or that a database could be rebuilt from the WAL alone. Both are wrong: transactions committed since the last checkpoint exist only in the WAL, and the WAL holds nothing older than that checkpoint. The resolutions now say to copy both files first, to checkpoint with the version that wrote the WAL, and what is lost if the WAL is deleted.

  • CLAUDE.md now matches the milestones (v2.0.2 final v2.x release, v3.0.0 format v8 and data integrity, v3.1.0 features) and documents error.rs, magic_sets.rs, fault_inject.rs and src/browser/. ROADMAP lists #405 and #407 in the v2.0.2 scope.

  • README: MSRV (1.89) stated, v2.x known issue (#371) shown near the top, Maven coordinates corrected to io.github.project-minigraf, Android listed on Maven Central, and binding download locations point to the binding repos.

  • .github/SECURITY.md lists 2.x as the supported line. About 50 broken wiki links in docs/ERROR_REFERENCE.md now use full wiki URLs.

  • Stability and support policy (#397). PHILOSOPHY.md §7 now states a format policy instead of "frozen for decades": what counts as a format change, that v(N+1) always reads v(N), and that older formats are dropped only in a major release. A new support policy gives v2.x data-integrity and security fixes for 12 months after v3.0.0 ships. "Production-ready", "12–15 months to production" and the check mark on "never losing data" are replaced with claims that hold today. SECURITY.md links the policy.

  • Support tiers (#400). The Rust crate and Python are Tier 1 (fully tested, released with every core release). Node.js, browser WASM, WASI, Java/JVM, Android, Swift and C are Tier 2 (experimental, best-effort releases). Tier 1 platforms are Linux ext4/xfs, macOS APFS and Windows NTFS on local disk. The README platform table shows each binding's tier.

  • Known-issues process (#399). Issues labelled data-integrity, corruption or durability stay open until their fix is in a published release (CONTRIBUTING.md). A new known-issue label and a pinned issue (#421) list each bug in the current release with affected versions, workaround and fix version; the README links it from a new "Known issues" section. #287 was reopened under this rule. The bug report template asks for the affected version range, binding and filesystem.

Known issues

  • Same-transaction multi-valued facts can read back as one value (#...
Read more
0