nornir 0.5.1

Companion to cargo: dependency tracking, release gating, deploy, benchmarks, and documentation assembly. Project-agnostic.
//! # nornir
//!
//! A project-agnostic companion to `cargo`. Reusable building blocks
//! for release pipelines, benchmarks, and documentation written in
//! Rust, callable from thin per-project driver binaries.
//!
//! Modules:
//! - [`bench`]      benchmark harness + append-only JSONL history
//! - [`release`]    gates, publish-order walker, deploy
//! - [`deps`]       cross-crate dependency tracking (workspace-aware)
//! - [`docs`]       deterministic assembly of README / CHANGELOG / etc.
//! - [`guard`]      AI-agent guard rails: chmod -w forbidden paths
//!                  declared in `[guard].forbidden` of a nornir.toml
//! - [`introspect`] call graph + dep graph + public API extracted from
//!                  the *built* code (rustdoc JSON, cargo metadata, syn);
//!                  rendered into docs so they cannot drift from the code
//!
//! Optional features:
//! - `fixtures-maven` — canonical Maven Central JAR set as bench inputs
//!
//! Future companion crates (not in this repo):
//! - `nornir-mcp` — exposes every API here as an MCP (Model Context
//!   Protocol) tool, letting an LLM agent drive build/release/docs.
//! - `nornir-cli` — `cargo nornir ...` subcommand for human use.
//!
//! See `workspace_holger/release/SPEC.md` for the contract that
//! generated binaries implement on top of this library.

// The viz app's whole-app `state_json()` is one large `serde_json::json!`
// object literal (one tab pane per key); each entry costs a level of the
// `json_internal!` macro recursion, so the default limit of 128 is exceeded once
// the tab set grows. 256 gives ample headroom for the pane keys.
#![recursion_limit = "256"]

pub mod arch;
pub mod autonom;
// Lifted into the `nornir-bench` leaf crate (its cli_outcome coupling is via
// nornir-cli-outcome; the live progress trail writes $NORNIR_VIZ_ACTIONLOG
// directly, so no viz/egui dep is pulled in); re-exported (with the
// `register_bench!` / `register_bench_ordered!` macros) so every
// `crate::bench::…` call site resolves unchanged.
pub use nornir_bench as bench;
pub use nornir_bench::{register_bench, register_bench_ordered};
pub mod change;
// Lifted into the `nornir-cli-outcome` leaf crate (pure serde); re-exported so
// every `crate::cli_outcome::…` call site resolves unchanged.
pub use nornir_cli_outcome as cli_outcome;
pub mod config;
// Lifted into the `nornir-coverage` leaf crate (cargo-llvm-cov run/report, pure
// serde; its warehouse/module couplings were doc-links only — the Iceberg
// persistence stays in nornir); re-exported so every `crate::coverage::…` call
// site resolves unchanged.
pub use nornir_coverage as coverage;
pub mod deepscan;
pub mod deps;
pub mod docs;
pub mod funnel;
// Lifted into the `nornir-git` leaf crate (gix + russh transport, no warehouse/
// embedder deps); re-exported so every `crate::gitio::…` / `crate::gitclean::…` /
// `crate::ssh::…` call site resolves unchanged.
pub use nornir_git::{gitclean, gitio, ssh};
// Lifted into the `nornir-guard` leaf crate (anyhow + sha2, its only cross-crate
// coupling is nornir-cli-outcome); re-exported so every `crate::guard::…` call
// site resolves unchanged.
pub use nornir_guard as guard;
#[cfg(feature = "embed-holger")]
pub mod holger_embed;
pub mod index;
pub mod introspect;
// Lifted into the `nornir-jobs` leaf crate (redb ledger + serde; its warehouse
// couplings were doc-links only — the concrete Iceberg persistence stays in
// nornir); re-exported so every `crate::jobs::…` call site resolves unchanged.
pub use nornir_jobs as jobs;
pub mod knowledge;
pub mod mimir;
pub mod monitor;
pub mod registry;
pub mod security;
// Lifted into the `nornir-selftest` leaf crate (bridges nornir-testmatrix);
// re-exported (with the `assert_emit!` macro) so every `crate::selftest::emit`
// and `assert_emit!` call site resolves unchanged.
pub use nornir_selftest as selftest;
pub use nornir_selftest::assert_emit;
// `ssh` moved into the `nornir-git` crate (re-exported above with gitio/gitclean).
pub mod release;
pub mod robot;
pub mod test_matrix;
#[cfg(feature = "vector")]
pub mod vector;
pub mod warehouse;
// `viz` builds the full egui app; `server` needs only `viz::model` (the pure
// timeline builder) for the `Viz.Timeline` RPC — submodules are gated inside.
#[cfg(any(feature = "viz", feature = "server"))]
pub mod viz;
pub mod workspace;

pub use anyhow::{Error, Result};

/// Process-wide serial lock for tests that mutate GLOBAL state (env vars, cwd, or
/// spawn `cargo metadata`). `cargo test` runs tests multi-threaded by default, so
/// two tests touching the same `HOME`/`NORNIR_VIZ_ACTIONLOG`/`cargo`-lock race
/// intermittently (each passes alone). Such tests take `let _g = nornir::serial_guard();`
/// at the top so they run one-at-a-time relative to each other. Poison-tolerant.
#[cfg(test)]
pub(crate) fn serial_guard() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
    LOCK.lock().unwrap_or_else(|e| e.into_inner())
}