Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nornir-graph/Cargo.toml

# nornir-graph — the constellation's ONE graph seam: two traits (BULK + QUERY),
# the pure graph math, and every graph-database backend that answers them.
#
# LEAF DISCIPLINE. The DEFAULT build has NO dependencies at all — std only — so a
# consumer that only wants the trait does not inherit a stack. (Contrast
# nornir-warehouse-trait, which despite its name pulls arrow 58 + iceberg + skade
# + uuid + chrono; every consumer of that seam inherits all of it. This crate must
# never become that.) Every backend that drags a dependency is behind an
# OFF-by-default feature, and `cargo tree` on the default feature set is the guard
# (tests/leaf_law.rs makes it mechanical).
[package]
name = "nornir-graph"
version = "0.2.0"
edition = "2021"
description = "The backend-neutral graph seam: a BULK trait (nodes/edges with pure SCC, topological order and reachability closure defaults) and a QUERY trait (contains/node/neighbours/pivot/shortest-path), domain-neutral over opaque string ids and a consumer-owned edge label, with in-memory, petgraph and embedded-Datalog (Cozo) backends behind features."
license = "MIT OR Apache-2.0"
repository = "https://git.gunnar.rs/nordisk/edda"
keywords = ["graph", "trait", "datalog", "topological-sort", "pathfinding"]
categories = ["data-structures", "algorithms"]

[features]
# The default build is a TRUE LEAF: std only, zero dependencies.
default = []
# Derives on the plain-data types (Edge).
serde = ["dep:serde"]
# `PetGraph`: a petgraph-backed BulkGraph whose `scc` is petgraph's OWN Tarjan —
# an independent second implementation, which is what makes the cross-check test
# able to go red.
petgraph = ["dep:petgraph"]
# `CozoGraph`: an embedded-Datalog GraphQuery/GraphStore, generic over the node
# payload and the edge label. OFF BY DEFAULT for the same reason every backend here
# is — LEAF DISCIPLINE. It drags an entire embedded database, so a consumer that
# only wants the trait must not inherit it. Rayon is NOT a reason — the vendored
# cozo has none; see the note on the dependency line.
cozo = ["dep:cozo"]

[dependencies]
serde = { version = "1", features = ["derive"], optional = true }
petgraph = { version = "0.8", optional = true }
# Embedded Datalog graph database.
#
# `default-features = false` drops cozo's `default` (= `compact` -> `minimal`),
# which bundles the SQLite C source. Without it cozo builds pure-Rust with its
# built-in `mem` engine plus the Datalog core, which is all this backend needs.
#
# ⚠ RAYON — REMOVED AT THE SOURCE. Not disabled, not excused, not off-by-default.
#
# (An earlier revision of this line documented an accepted "ROOT LAW #0 exception".
# That is no longer true and the text is gone. A stale comment claiming an exception
# that no longer exists is the disease this house hunts.)
#
# `cargo tree -p nornir-graph --all-features -i rayon` finds NOTHING. That is the
# acceptance test, and `tests/rayon_free_law.rs` re-takes it mechanically — under
# `--all-features`, because this backend is OFF BY DEFAULT and a guard that looked
# only at the default feature set would have passed serenely for the entire period
# the route existed.
#
# THE PROBLEM, which is real and is upstream's: cozo 0.7.6 declares `rayon` as an
# OPTIONAL dependency (only its `graph-algo` feature enables it) but USES rayon
# UNCONDITIONALLY in its own source, so it does not compile without it:
#
#   error[E0433]: cannot find module or crate `rayon`
#     --> cozo-0.7.6/src/lib.rs:511:9
#      |  rayon::spawn(move || db.run_multi_transaction(...))
#   error[E0599]: no method named `par_iter` found for reference
#                 `&BTreeMap<MagicSymbol, CompiledRuleSet>`
#     --> cozo-0.7.6/src/query/eval.rs:200:26  (and :275:26)
#
# The only configuration of published cozo 0.7.6 that COMPILES is therefore the one
# that puts rayon in the tree. So the crate is vendored and patched:
# `vendor/cozo-rayonfree` (a workspace member; its Cargo.toml header is the
# authoritative record). Three changes, and `rayon` deleted from its manifest:
#   * the two `prog.par_iter()` rule-evaluation sites -> `prog.iter()`;
#   * `rayon::spawn` -> `std::thread::spawn` (one fire-and-forget blocking task, not
#     a fan-out — upstream already does exactly this on wasm32);
#   * `graph-algo` and `src/fixed_rule/algos/` DELETED. This one was forced, not
#     chosen: `graph` 0.3 and `graph_builder` declare rayon as a HARD, non-optional
#     dependency, so there is no "keep the feature, drop rayon". It costs cozo's ~22
#     built-in graph fixed rules. `CozoGraph::shortest_path` never used them — it is
#     written out in Datalog, so it agrees tie-for-tie with the in-memory backend
#     rather than merely claiming to.
#
# GOING SERIAL WAS MEASURED, NOT ASSUMED (2026-08-01, oden). Three variants of those
# two sites — upstream rayon, serial, and `gatling` — timed on a 15,000-node /
# 44,850-edge graph with a 300-deep recursive min-hop query (this crate's own
# `distances` script), 31 reps x 3 interleaved rounds, identical result fingerprints
# verified first:
#
#     serial    p50 ~63.4 ms   p99 ~66.2 ms    <- fastest, and now shipped
#     rayon     p50 ~66.2 ms   p99 ~72.9 ms
#     gatling   p50 ~76.0 ms   p99 ~80.7 ms
#
# The fan-out width is N = 1 in 15,764 of 16,065 calls. `prog` holds the compiled
# rule sets for ONE stratum of ONE query, and the loop is re-entered once per
# semi-naive epoch, so upstream was paying pool overhead thousands of times to
# "parallelise" a single item. Serial is not a concession here. It is the fast path,
# and gatling — built for wide balanced fan-outs — is slower still at N=1.
#
# WHAT WOULD RETIRE THE FORK: upstream fixing the optional/unconditional mismatch —
# gating `rayon::spawn` and the two `par_iter` sites behind `#[cfg(feature = "rayon")]`
# with the serial fallback they already have for wasm. On that day, delete
# `vendor/cozo-rayonfree`, drop it from the workspace members, and this line becomes
# `cozo = { version = "0.7", optional = true, default-features = false }`.
cozo = { package = "cozo-rayonfree", version = "0.7.6", path = "../../vendor/cozo-rayonfree", optional = true, default-features = false }

[dev-dependencies]
# The cross-backend identity tests need BOTH database backends present.
nornir-graph = { path = ".", features = ["petgraph", "cozo", "serde"] }