Repository navigation
Releases
v2.0.1 — 2026-09-25
Release Notes
Patch release on the v2.x line. File format is unchanged (v7); no API changes.
Fixed
- Entity-bound lookups could silently return nothing after an index rebuild on open (#370). When the index checksum does not match on open (for example after a process was killed during
save()), Minigraf rebuilds all four indexes from the fact pages. The rebuild computed each fact's location by re-packing all facts contiguously, but a file written by several checkpoints has partially filled pages that a contiguous re-pack does not reproduce. From the second checkpoint onward, rebuilt index entries pointed at the wrong page/slot: entity-bound queries ([:some/ident ?a ?v]) returned[]or errored while attribute-driven queries still worked, andcheckpoint()copied the bad entries forward. The rebuild now reads each fact's real on-disk location (#372).
Known issues
- Files whose indexes were already rebuilt with wrong locations by v2.0.0 are not repaired by this release. A public integrity check and rebuild-indexes-from-fact-pages operation is tracked in #373; a crash-atomic
save()in #374. - Two values of the same attribute for one entity written in a single
transact(or retracted in a singleretract) can read back as one value, differently per query path (#371, #287). The fix changes the index key layout (file format v8) and ships in v3.0.0. Workaround on v2.x: write or retract each value of a multi-valued attribute in its own call.
Download minigraf 2.0.1
| 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.0 — 2026-08-26
Release Notes
This was originally slated as v1.3.0, matching the GitHub milestone name under
which most of this work was tracked. It ships as v2.0.0 instead: this
project's CHANGELOG header (above) commits to Semantic Versioning, and under
strict SemVer any backward-incompatible change to the public API past 1.0.0
requires a major version bump, not a minor one. The breaking changes below —
a public method return-type change, two OpenOptions struct-literal breaks
plus #[non_exhaustive] closing that gap for good, Windows lock-semantics
changes, and an MSRV bump — are exactly that. GitHub milestones v1.4.0,
v1.5.0, v1.6.0, and the pre-existing untitled "2.0" (format v8 + window
completeness + FFI expansion) were renumbered to v2.1.0, v2.2.0, v2.3.0, and
v3.0.0 respectively to keep the sequence consistent.
Breaking-ish changes
-
Adding
OpenOptions::allow_unlockedbroke struct-literal construction.
At the time this field landed,OpenOptionshad all-public fields and was
not#[non_exhaustive], so under Rust's semver rules any downstream code
building it as
OpenOptions { wal_checkpoint_threshold: .., page_cache_size: .., .. }
without..Default::default()no longer compiled. Callers who spread
..Default::default(), or who use the chainable builder methods, were
unaffected. This branch had to update two such literals in its own tree —
theDefaultimpl itself and one test — and only the latter is a
"downstream literal" in the sense that matters to a consumer.This was the first post-1.0 field addition to
OpenOptions. The
earliermax_derived_factsandmax_resultsfields landed in v0.19.0,
before 1.0, so they set no precedent under the stability guarantee in
PHILOSOPHY.md §7. Combined with thesynchronousfield below, it's why
OpenOptionsis now#[non_exhaustive]— see that entry. -
OpenOptionsgains a second post-1.0 field:synchronous(#302). Same semver consequence asallow_unlockedabove — a struct-literalOpenOptions { .. }built without..Default::default()no longer compiles. One in-tree literal (src/db.rs, a test) needed updating; every other in-tree construction already used the spread form or the chainable builder. -
OpenOptionsis now#[non_exhaustive]. This is a stronger break than the two field additions above: it disallows any struct-literal construction from outside this crate, including the..Default::default()spread form that made those additions non-breaking for spread-form callers. Construct viaOpenOptions::new()/OpenOptions::default()plus the chainable builder methods instead. Landed alongside theallow_unlocked/synchronousfields specifically so this is the last time a newOpenOptionsfield forces a major-version bump. A newwal_checkpoint_threshold(n)builder method was added in the same change — the field had none before, which is why every in-tree construction site (tests, benches, one example) used a struct literal for it and had to be converted to the builder form. -
Minimum supported Rust version is now 1.89 (was effectively 1.85, implied
byedition = "2024"). Required forstd::fs::File::try_lock, stabilised in
1.89. Declared asrust-versioninCargo.toml, so cargo reports a clear
error rather than a confusing one. -
On Windows, an open database can no longer be read through any other file
handle — including another handle in the same process.LockFileExlocks
are mandatory rather than advisory and exclude every handle but the one
holding them, so copying or backing up an open.graphfile now fails on
Windows where it previously succeeded, and so does opening it yourself for a
raw read while the database is live. The error is os error 33, "another
process has locked a portion of the file", even when the other process is
you. Unix locks remain advisory and are unaffected. Close the database
before reading the file directly — which also avoids a torn read. -
open()now fails on filesystems that cannot lock at all rather than
proceeding unprotected. SetOpenOptions::allow_unlocked(true)to accept the
risk on single-writer deployments. -
This detection does not cover NFSv3 exports mounted with
-o nolock
(#334). Linux's NFS client servesflock/OFD locks for anolockmount out
of its own local, in-kernel lock table instead of contacting the server, so
try_lockreports an ordinary local success and looks identical to a mount
where locking genuinely works —allow_unlockedis never consulted because
the code never learns the mount can't really lock. Two separate client hosts
writing the samenolockexport can each believe they hold the lock with no
cross-host coordination. There is no way to detect this from the lock call's
result, sonolockNFSv3 exports are an unsupported deployment for
multi-writer use — the same category as running mixed Minigraf versions
against one file, below. Seedocs/ERROR_REFERENCE.mdSTG-027. -
A database genuinely locked by another process now reports its error
later than before — about 375ms later.FileBackend::open_withretries a
cross-processWouldBlockup to 10 times with backoff doubling from 5ms and
capped at 50ms before concluding another process holds the lock. This exists
becauseforktransiently duplicates the lock-holding descriptor into a
child untilexecvecloses its close-on-exec descriptors, so any process
that spawns subprocesses — the normal pattern for the Python and Node
bindings — could see a spuriousWouldBlockfrom its own child. SQLite
solves the same problem withbusy_timeout; this is the same idea, bounded
so a real conflict still fails, just not instantly. A same-process conflict
(this process already holds the file open) is unaffected: it fails
immediately, since waiting could never help. -
A refused open on a filesystem that cannot lock may leave a 0-byte
.graphfile behind. The file must exist before it can be locked, so the
create-then-lock ordering now creates before it can know the lock will be
refused. Harmless — the next open seesis_newand initialises normally —
but worth knowing if you inspect the filesystem after a failed open. -
Minigraf's,WriteTransaction's, andPreparedQuery's public methods now
returnResult<T, MinigrafError>instead ofanyhow::Result<T>(#277).
MinigrafErrorimplementsstd::error::Error(so?still works against
anyhow::Result/Box<dyn Error>callers) and adds.category() -> ErrorCategory
and.code() -> &'static strfor structured error matching. This lands the
foundation for issue #277; every error currently surfaces as the generic
INT-000code until each error category's follow-up PR wires up its real
docs/ERROR_REFERENCE.mdcodes.
Every error'sDisplay/to_string()output now also carries a[CODE]
prefix (e.g.[INT-000] unclassified internal error: ...) that it did not
have before — a behavior change beyond the type signature. Anything that
string-matches error output is affected, including the interactive REPL's
printed error messages and all 7 language-binding repos (Python, Node,
WASM, Java, Android, Swift, C). -
src/query/datalog/parser.rsnow carries realPRS-0xxcodes instead of
falling back toINT-000(#358, #277 2/6). All 79 documentedPRS-0xx
codes are wired up viabail_coded!/err_coded!at every parser call site
that maps onto one; a parse error's.code()is now e.g."PRS-005"
("Unclosed list") instead of the generic"INT-000", and its
Display/to_string()prefix changes to match.parser.rs's internal
signatures changed fromResult<_, String>toanyhow::Result<_>
(crate-internal only — the publicdb.execute()/db.prepare()surface is
unchanged, stillResult<T, MinigrafError>). Two documented codes,
PRS-028 ("window expression cannot be empty") and PRS-049 ("unexpected end
of fact vector"), are wired up but unreachable through the public API — the
has_over/len() >= 4conditions that guard their call sites already
guarantee the "bad" case can't occur, so no input reaches them. This is a
pre-existing property of the parser logic, not something this PR changed.
Two static message strings changed wording to match their
docs/ERROR_REFERENCE.md"Error text" verbatim (previously drifted):
"Retract argument must be a vector"→"...must be a vector of facts"
(PRS-044). Anything that string-matches parser error output — including
the interactive REPL and all 7 language-binding repos — is affected by
both the code prefix and this wording change. -
Storage/file-format errors now surface their real
STG-0xxcode (#359,
part of #277's rollout): all 27 documentedSTG-0xxcodes are wired up
acrosssrc/storage/{mod,persistent_facts,btree,btree_v6,packed_pages}.rs
andsrc/storage/backend/file.rs— header validation (STG-001–STG-009),
header re-read failures (STG-010), on-disk B+tree/packed-page corruption
(STG-011–STG-015), a poisoned backend mutex (STG-016), page-count
arithmetic overflow guards (STG-017–STG-024), and the three lock
conflict errors (STG-025–STG-027).docs/ERROR_REFERENCE.md's STG
section's "Error text" lines were rewritten from one-off example values to
canonical{}-placeholder templates so they byte-for-byte match the
REGISTRYentries the sync test checks against.
FileBackend::open_withno longer collapses a more specific header-parse
failure (e.g. a bad magic number) into the genericSTG-010— it now
propagates aCodedErroralready present in the chain as-is, only falling
back toSTG-010for a genuinely uncoded (e.g. raw I/O) failure.
Not covered by a dedicated regression test:STG-011's call site is
guarded by adebug_assert!that always fires first in a debug/test
build, so it's release-build-only and unre...
v1.2.3 — 2026-08-10
Release Notes
No changes to the core crate. Released so the language bindings have a version
to republish on, carrying a fix that lives in a binding rather than here.
v1.2.2 made a second handle on an already-open file an error (#304). The
UniFFI bindings and the C API already had a way to release a handle on demand
(destroy() and minigraf_close respectively), but the Node binding did not,
and JavaScript has no deterministic destructor — so reopening a .graph file
in one process became impossible there. minigraf-node gained a close()
method (project-minigraf/minigraf-node#1); this release is what lets it ship.
Binding versions track the core version, so all seven bindings republish at
1.2.3.
Download minigraf 1.2.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 |
v1.2.2 — 2026-08-10
Release Notes
Drop-in replacement for v1.2.1. No file-format changes, no public API changes.
Bug fixes
-
Two handles on one file in the same process (#304):
FileLock::acquiretreated a lock file whose holder PID equalled our own as stale, deleted it, and opened anyway — on the theory that it could only be a leaked handle. The far more likely cause is a handle that is open and in use right now elsewhere in the process, so the "self-heal" silently produced two liveFileBackends on one file. Each caches its ownheader.page_count, allocates new pages from that count, and bounds-checksread_pageagainst it, so the two page tables diverge. That — not an allocation off-by-one — is the source of the intermittentPage N out of bounds (total pages: M). It also corrupted the lock itself: whichever handle dropped first ranFileLock::dropand removed the lock file that by then belonged to the survivor, leaving a live handle unlocked and admitting other processes too. A lock held by our own PID is now refused with an error naming the same-process case; a lock held by a different, dead process is still reclaimed as before. Behaviour change: callers that relied on reopening a path they already hold open now get an error instead of silent corruption, and should reuse the existing handle (Minigrafis cheap to clone and all clones share one database). -
String comparison in predicates:
[(< ?a ?b)],>,<=and>=now order two strings lexicographically instead of failing. Previously every ordering predicate routed throughto_float_pair, which errors on non-numeric operands, so a string comparison silently matched no rows — and because both directions returned nothing, a range filter dropped the entire relation rather than partitioning it. This most often bit ISO-8601 timestamps stored as strings. The predicate path now agrees withvalue_lt/value_cmp, whichmin,maxand:order-byalready use. Mixed-type comparison (string vs number) remains an error, and NaN comparisons remain false.
Download minigraf 1.2.2
| 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 |
v1.2.1 — 2026-06-28
Release Notes
Drop-in replacement for v1.2.0. No file-format changes, no public API changes.
Bug fixes
- Magic sets
fbadornment: queries with a literal keyword in value position (e.g.(reports-to ?emp :alice)) now return correct results instead of empty results (#298). Root cause: seed fact used an ephemeral random UUID as entity; the 1-arg guard bound the variable to that UUID rather than the keyword value. Fixed with a deterministic sentinel entity (Uuid::new_v5) and a 2-arg guard that binds from value position. - Magic sets recursive rules:
(reach :a ?y)with intermediate variable?midand keyword entity binding now evaluates correctly (#297).
Download minigraf 1.2.1
| 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 |
v1.2.0 — 2026-06-26
Release Notes
Drop-in replacement for v1.1.x. No file-format changes, no public API breaking changes. Upgrading requires no code changes.
Features
- Magic Sets rewriting — recursive Datalog queries with bound arguments are now automatically rewritten top-down via magic sets, propagating bound values into recursive rules to avoid full-relation scans (#289)
- Adornment classification, seed fact generation, magic guard injection, SCC-aware propagation rule generation, full
rewrite()wired intoexecute_query_with_rules - Limitation: mutual recursion through negation is not rewritten (documented in ROADMAP §9.6)
- Adornment classification, seed fact generation, magic guard injection, SCC-aware propagation rule generation, full
- Per-query complexity limits —
:max-derived-factsand:max-resultsclauses added toquery; global default raised to 1,000,000 derived facts (#288, #290)
Bug fixes
selective_fact_fetch: includeassertedflag in dedup key — previously retracted facts could shadow live facts under certain access patterns (#285, #286)selective_fact_fetch: restore per-pattern entity priority lost in a prior refactor (#283)- v5→v6 migration: fix hang caused by using
header.page_countas the B-tree start page instead of the correct offset (#272)
Performance
- Eliminate backend mutex hold on cache hits in
CommittedFactLoaderImpl::resolve— read path no longer acquires the write lock when the page is cached (#279) - Pre-build
MutexStorageBackendinCommittedFactLoaderImplto eliminate oneArc::cloneperresolve()call (#280, #281)
Infrastructure
- Split Java, Android, Swift, and C bindings into independent repos under
project-minigraforg — completes #231 repo split (#231 phase 2)minigraf-java: https://github.com/project-minigraf/minigraf-javaminigraf-android: https://github.com/project-minigraf/minigraf-androidminigraf-swift: https://github.com/project-minigraf/minigraf-swiftminigraf-c: https://github.com/project-minigraf/minigraf-c
minigraf-ffiyanked from crates.io — UniFFI layer inlined into each binding repo's private shim; use the per-language packages insteadcascade.ymlupdated to dispatchcore-releaseto all 7 binding repos on every version tag- All binding repos migrated to OIDC trusted publishing —
NPM_TOKENandCARGO_REGISTRY_TOKENsecrets removed; crates.io usesrust-lang/crates-io-auth-action@v1, npm usessetup-node@v6OIDC - Python, Node, and WASM binding repos now include a
preparejob that pins theminigrafdependency version inCargo.tomland commits before building — consistent with Java, Android, Swift, and C
Documentation
- Add
docs/ERROR_REFERENCE.md: full inventory of user-facing errors (PRS/QRY/STG/WAL/API categories, 113 entries) with cause, resolution steps, and bad-input examples; docs-only reference codes PRS-001…API-009 (#192)
Download minigraf 1.2.0
| 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 |
v1.1.1 — 2026-05-17
Release Notes
Patch release. Fixes cargo-dist Windows build failure that prevented REPL binaries and crates.io publish from completing for v1.1.0. No code changes to the library itself.
Build
- Exclude
fuzzcrate from workspace so cargo-dist can build on Windows (MSVC linker incompatible with#![no_main]libFuzzer targets) — fixes #263
Download minigraf 1.1.1
| 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 |
v1.1.0
v1.1.0 — drop-in replacement for v1.0.0
No file-format changes, no public API changes, no query surface changes. Upgrading requires no code changes.
Performance
- Hash-join replaces nested-loop join for multi-clause queries — O(N) instead of O(N²) for large fact sets (#202, #203, #204)
- Selective B+Tree fact fetch: queries with bound entity/attribute skip full-scan (#208)
- Predicate push-down into join ordering (#207)
- Cost-based clause ordering for
not/orrules (#205, #206) - SIMD crossover analysis and benchmarking infrastructure (#229)
Bug fixes
- Critical correctness and durability bugs from deep audit (#225)
- Read-only handle
Dropno longer modifies the file on close (#226) QueryResult::Transacted/Retractedrestored to tuple variants — struct-variant form was a breaking change (#261)- New additive
Minigraf::current_tx_count() -> u64exposes the:as-ofmonotonic counter
Reliability
- WAL fault injection + crash-recovery matrix (#209, #210, #214)
- Storage/migration resilience, index corruption recovery, concurrency stress (#215, #216, #217)
- Property-based query correctness tests, fuzz targets, per-module coverage gates (#212, #213, #219)
- Long-haul smoke suite, XTDB/Datomic semantic compatibility (#220, #221)
- Workspace-wide clippy enforcement, grammar conformance harness (#232, #233)
v1.0.0 — Phase 8 Complete (2026-05-01)
Release Notes
Milestone
This is the v1.0.0 release. The public Rust API and the .graph file format are now stable
and committed to semantic versioning. File format stability is guaranteed from this release.
Phase 8 summary
All Phase 8 cross-platform targets have shipped:
- 8.1a — Browser WASM (
BrowserDb,IndexedDbBackend,@minigraf/browseron npm) — v0.20.0 - 8.1b — Server-side WASM (
wasm32-wasip1/ WASI, Wasmtime/Wasmer CI) — v0.20.0 - 8.2 — Mobile bindings (Android
.aaron GitHub Packages, iOS.xcframeworkvia SPM, UniFFI) — v0.21.0 - 8.3a — Python (
minigrafon PyPI, pre-built wheels) — v0.22.0 - 8.3b — Java/JVM (
io.github.adityamukho:minigraf-jvmon Maven Central, fat JAR) — v0.23.0 - 8.3c — C FFI (
minigraf.h+ platform tarballs on GitHub Releases) — v0.24.0 - 8.3d — Node.js (
minigrafon npm, pre-built.nodebinaries) — v0.25.0
Also in this release
pkg/renamed tominigraf-wasm/,swift/renamed tominigraf-swift/— consistent
top-level naming across all workspace packages (issue #179)@minigraf/browsernow published to npm on every tagged release (issue #179)@minigraf/wasipublished to npm on every tagged release (issue #178) — WASI binary packaged for Node.js WASI consumers- Per-platform READMEs added:
minigraf-wasm/,minigraf-node/,minigraf-ffi/python/,
minigraf-c/,minigraf-ffi/java/
Tests
795 tests passing (788 passing + 7 ignored: confirmed or+neg-cycle stratification bug,
deferred to post-1.0 backlog).
Download minigraf 1.0.0
| 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 |
v0.25.0 — Phase 8.3d: Node.js Bindings
Phase 8.3d: Node.js bindings published to npm as minigraf. Install with npm install minigraf. Prebuilt .node binaries for Linux x86_64/aarch64, macOS universal2, Windows x86_64. Full TypeScript definitions included.