Skip to content

Releases

v2.0.1 — 2026-09-25

Choose a tag to compare

@github-actions github-actions released this 25 Sep 05:19

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, and checkpoint() 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 single retract) 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

Choose a tag to compare

@github-actions github-actions released this 26 Aug 05:27

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_unlocked broke struct-literal construction.
    At the time this field landed, OpenOptions had 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 —
    the Default impl 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
    earlier max_derived_facts and max_results fields landed in v0.19.0,
    before 1.0, so they set no precedent under the stability guarantee in
    PHILOSOPHY.md §7. Combined with the synchronous field below, it's why
    OpenOptions is now #[non_exhaustive] — see that entry.

  • OpenOptions gains a second post-1.0 field: synchronous (#302). Same semver consequence as allow_unlocked above — a struct-literal OpenOptions { .. } 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.

  • OpenOptions is 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 via OpenOptions::new()/OpenOptions::default() plus the chainable builder methods instead. Landed alongside the allow_unlocked/synchronous fields specifically so this is the last time a new OpenOptions field forces a major-version bump. A new wal_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
    by edition = "2024"). Required for std::fs::File::try_lock, stabilised in
    1.89. Declared as rust-version in Cargo.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. LockFileEx locks
    are mandatory rather than advisory and exclude every handle but the one
    holding them, so copying or backing up an open .graph file 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. Set OpenOptions::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 serves flock/OFD locks for a nolock mount out
    of its own local, in-kernel lock table instead of contacting the server, so
    try_lock reports an ordinary local success and looks identical to a mount
    where locking genuinely works — allow_unlocked is never consulted because
    the code never learns the mount can't really lock. Two separate client hosts
    writing the same nolock export 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, so nolock NFSv3 exports are an unsupported deployment for
    multi-writer use — the same category as running mixed Minigraf versions
    against one file, below. See docs/ERROR_REFERENCE.md STG-027.

  • A database genuinely locked by another process now reports its error
    later than before
    — about 375ms later. FileBackend::open_with retries a
    cross-process WouldBlock up to 10 times with backoff doubling from 5ms and
    capped at 50ms before concluding another process holds the lock. This exists
    because fork transiently duplicates the lock-holding descriptor into a
    child until execve closes its close-on-exec descriptors, so any process
    that spawns subprocesses — the normal pattern for the Python and Node
    bindings — could see a spurious WouldBlock from its own child. SQLite
    solves the same problem with busy_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
    .graph file 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 sees is_new and initialises normally —
    but worth knowing if you inspect the filesystem after a failed open.

  • Minigraf's, WriteTransaction's, and PreparedQuery's public methods now
    return Result<T, MinigrafError> instead of anyhow::Result<T>
    (#277).
    MinigrafError implements std::error::Error (so ? still works against
    anyhow::Result/Box<dyn Error> callers) and adds .category() -> ErrorCategory
    and .code() -> &'static str for structured error matching. This lands the
    foundation for issue #277; every error currently surfaces as the generic
    INT-000 code until each error category's follow-up PR wires up its real
    docs/ERROR_REFERENCE.md codes.
    Every error's Display/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.rs now carries real PRS-0xx codes instead of
    falling back to INT-000
    (#358, #277 2/6). All 79 documented PRS-0xx
    codes are wired up via bail_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 from Result<_, String> to anyhow::Result<_>
    (crate-internal only — the public db.execute()/db.prepare() surface is
    unchanged, still Result<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() >= 4 conditions 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-0xx code (#359,
    part of #277's rollout): all 27 documented STG-0xx codes are wired up
    across src/storage/{mod,persistent_facts,btree,btree_v6,packed_pages}.rs
    and src/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
    REGISTRY entries the sync test checks against.
    FileBackend::open_with no longer collapses a more specific header-parse
    failure (e.g. a bad magic number) into the generic STG-010 — it now
    propagates a CodedError already present in the chain as-is, only falling
    back to STG-010 for a genuinely uncoded (e.g. raw I/O) failure.
    Not covered by a dedicated regression test: STG-011's call site is
    guarded by a debug_assert! that always fires first in a debug/test
    build, so it's release-build-only and unre...

Read more

v1.2.3 — 2026-08-10

Choose a tag to compare

@github-actions github-actions released this 10 Aug 06:07

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

Choose a tag to compare

@github-actions github-actions released this 10 Aug 05:21

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::acquire treated 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 live FileBackends on one file. Each caches its own header.page_count, allocates new pages from that count, and bounds-checks read_page against it, so the two page tables diverge. That — not an allocation off-by-one — is the source of the intermittent Page N out of bounds (total pages: M). It also corrupted the lock itself: whichever handle dropped first ran FileLock::drop and 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 (Minigraf is 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 through to_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 with value_lt / value_cmp, which min, max and :order-by already 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

Choose a tag to compare

@github-actions github-actions released this 28 Jun 12:46

Release Notes

Drop-in replacement for v1.2.0. No file-format changes, no public API changes.

Bug fixes

  • Magic sets fb adornment: 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 ?mid and 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

Choose a tag to compare

@github-actions github-actions released this 26 Jun 11:37

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 into execute_query_with_rules
    • Limitation: mutual recursion through negation is not rewritten (documented in ROADMAP §9.6)
  • Per-query complexity limits — :max-derived-facts and :max-results clauses added to query; global default raised to 1,000,000 derived facts (#288, #290)

Bug fixes

  • selective_fact_fetch: include asserted flag 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_count as 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 MutexStorageBackend in CommittedFactLoaderImpl to eliminate one Arc::clone per resolve() call (#280, #281)

Infrastructure

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

Choose a tag to compare

@github-actions github-actions released this 17 May 06:25
80a9154

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 fuzz crate 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

Choose a tag to compare

@adityamukho adityamukho released this 17 May 05:49
aa77095

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/or rules (#205, #206)
  • SIMD crossover analysis and benchmarking infrastructure (#229)

Bug fixes

  • Critical correctness and durability bugs from deep audit (#225)
  • Read-only handle Drop no longer modifies the file on close (#226)
  • QueryResult::Transacted/Retracted restored to tuple variants — struct-variant form was a breaking change (#261)
  • New additive Minigraf::current_tx_count() -> u64 exposes the :as-of monotonic 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)

Choose a tag to compare

@github-actions github-actions released this 02 May 03:49

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/browser on npm) — v0.20.0
  • 8.1b — Server-side WASM (wasm32-wasip1 / WASI, Wasmtime/Wasmer CI) — v0.20.0
  • 8.2 — Mobile bindings (Android .aar on GitHub Packages, iOS .xcframework via SPM, UniFFI) — v0.21.0
  • 8.3a — Python (minigraf on PyPI, pre-built wheels) — v0.22.0
  • 8.3b — Java/JVM (io.github.adityamukho:minigraf-jvm on 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 (minigraf on npm, pre-built .node binaries) — v0.25.0

Also in this release

  • pkg/ renamed to minigraf-wasm/, swift/ renamed to minigraf-swift/ — consistent
    top-level naming across all workspace packages (issue #179)
  • @minigraf/browser now published to npm on every tagged release (issue #179)
  • @minigraf/wasi published 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

Choose a tag to compare

@adityamukho adityamukho released this 26 Apr 03:42
c8b221f

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.