# The workspace. Until 2026-08-30 this manifest was a bare `[package]` and cargo
# treated it as a one-member workspace by implication; the members list is
# explicit now because there is a second member: `crates/modgunn-build-thing`,
# this repo's `impl BuildThing` (lane AG). Nothing modgunn ships links it — it
# exists so `.nornir/build-variants.toml` can be EMITTED from code instead of
# hand-written, and dwarves' forge finds it by package name. `xtask/` keeps its
# own `[workspace]` and is deliberately NOT a member here.
[workspace]
resolver = "2"
members = [".", "crates/modgunn-build-thing"]
[package]
name = "modgunn"
version = "0.0.8"
edition = "2021"
description = "Móðguðr — the nordisk security + SCIP world-delta gatekeeper. Guards the bridge (Gjallarbrú): scrutinizes what passes (CVE/license/provenance verdict) and tracks every change in the dependency world. Shared by holger + nornir to break their dependency cycle."
license = "MIT OR Apache-2.0"
repository = "https://git.gunnar.rs/mikronova/modgunn"
# Development notes, agent instructions and machine-local tooling stay in the
# repository; they are not part of the published crate.
exclude = [
".nornir/",
".cargo/",
"docs/",
"brokkr.toml",
"bench_history.jsonl",
".gitattributes",
"tests/build_variants.rs",
]
# Two engines behind features so a lean consumer (holger's gate) pulls only Engine A,
# while the heavy world-delta site (128GB–1TB RAM) opts into Engine B.
# `provenance-verify` rides in `default` too: holger's lib/ui deps both already pin
# `default-features = false` + an explicit feature list (see their Cargo.tomls), so
# they are unaffected either way; but any OTHER consumer (e.g. nornir, which takes
# modgunn's default plus `warehouse`/`rustsec`) must not be able to silently run a
# provenance policy gate against the scoring-only path — a plain build now compiles
# the REAL verifier in, closing that misconfiguration.
[features]
default = ["scan", "provenance-verify"]
# Engine A — artifact security VERDICT: multi-ecosystem CVE / license / provenance.
# Lean; this is what holger's policy gate + static-holger quarantine consume.
scan = []
# Engine B — SCIP whole-world dep-tree BEHAVIOUR-DELTA + tripwires / graph-algs /
# anomaly detection. Heavy by design (government "knows exactly what happens to the
# smallest bit"). Dynamic plane only; never compiled into static-holger.
scip = []
# Iceberg warehouse access **over skade only** (never nornir — that would recreate
# the cycle modgunn exists to break). OFF by default so holger's lean policy gate +
# static-holger stay pure-leaf (no arrow/iceberg/redb tree). The world-delta site
# (Engine B) and nornir opt in. modgunn reads nornir's sbom_components table
# and OWNS its own (cve_advisories, world_snapshots, behavior_deltas,
# tripwire_events) in the same physical catalog / `nornir` namespace.
warehouse = ["dep:skade", "dep:tokio", "dep:anyhow", "dep:uuid", "dep:tempfile", "dep:nornir-zone"]
# Fleet-join PUSHDOWN over the DataFusion `sql` surface (P5 variant B). Heavy:
# pulls the DataFusion tree (+ the C liblzma via async-compression), so it is a
# separate opt-in on top of `warehouse` for the S3/S4 scale where loading the whole
# advisory db into RAM stops being affordable. The lean gate never enables it.
# The surface moved OUT of skade on 2026-08-14 (skade 0369d66, "SQL moves out of
# skade so skade can publish again"): `skade/sql` no longer exists, so this rides
# the sibling `skade-sql` crate instead — same `Warehouse::sql`, now a trait.
warehouse-sql = ["warehouse", "dep:skade-sql"]
# RUSTSEC advisory-db markdown import (`osv::parse_rustsec_md`). Pulls the `toml`
# parser, so it is OFF by default to keep the lean `scan` gate (holger / static-
# holger) free of toml — nornir's advisory importer opts in alongside `warehouse`.
# The pure patched/unaffected → range inversion (`osv::rustsec_affected_ranges`)
# is ungated; only the toml front-matter parse needs this feature.
rustsec = ["dep:toml"]
# Multi-ecosystem manifest / lockfile INGEST (`manifests` module): parse a
# project's dependency manifest/lockfile into modgunn's shared SBOM component
# model — one parser per ecosystem (cargo/pip/npm/maven/go/nuget/gem). Pure-Rust,
# zero-shell. OFF by default so the lean `scan` gate (holger / static-holger)
# never pulls the `toml` + `roxmltree` parsers this needs; nornir's SBOM capture
# opts in. Implies `scan` (components land in `sbom::SbomComponent`).
manifests = ["scan", "dep:toml", "dep:roxmltree"]
# Build-provenance VERIFY (Tier-1 bug #2): OPEN a znippy-sealed
# `<artifact>.build-provenance.znippy` (the blob nornir's `seal::dump_beside_artifact`
# writes), VERIFY its detached-CMS seal + content digest against the trusted key(s),
# and PRODUCE a genuinely-verified provenance `Attestation` for
# `provenance::ProvenanceRequirement::evaluate` (instead of assuming one). OFF by
# default so the lean `scan` gate (holger / static-holger) pulls neither the
# RustCrypto CMS stack nor the znippy archive reader. Reuses the SAME upstream
# crates nornir's seal uses — the dep-light `nornir-build-thing` (attestation types +
# canonical digest; it depends on NEITHER nornir NOR modgunn, so no cycle) and
# `znippy-common`'s pure-Rust `sign` verify — so the sealed-blob format matches
# byte-for-byte. Implies `scan` (the verifier lives in `provenance`).
provenance-verify = ["scan", "dep:nornir-build-thing", "dep:znippy-common", "znippy-common/sign", "dep:anyhow"]
# `test-support` — expose the nornir-owned `sbom_components` WRITER
# (`Warehouse::seed_sbom_components`) so an OUT-OF-CRATE end-to-end test (the
# `tests/cli_warehouse.rs` CLI subprocess harness) can stand in for nornir's SBOM
# capture and drive the real `warehouse scan` / `--since` CDC-delta / `--vex`
# suppression paths against a genuine warehouse. OFF by default and NEVER in a
# production build — modgunn only READS `sbom_components` in production; this writer
# exists solely to seed a fixture warehouse in tests. Rides on `warehouse` (it needs
# the skade seam). In-crate unit tests get the same writer for free via `cfg(test)`.
test-support = ["warehouse"]
# ── Common framework (About + Tools menu + service lifecycle) ──
# modgunn's slice of the JetBrains-style, constellation-wide COMMON FRAMEWORK that
# every suite app shares: a **Tools menu** of **jera**-backed demo actions (start a
# container / generate demo advisory data THROUGH `jera::container::ContainerSpec` +
# the shared job ledger — never a bespoke docker call or `thread::spawn`) and
# modgunn's service **lifecycle** declared as a **skidbladnir** `ServiceSpec` driven
# through the shared `Lifecycle` (the ONE systemd renderer + a dedicated `modgunn`
# system user). Both CONSUMED from the shared crates, never twinned (L5). OFF by
# default so the lean `scan` gate (holger / static-holger) pulls neither jera nor
# skidbladnir; the CLI (`modgunn about|tools|demo|service`) + a framework-aware
# consumer opt in. The About identity itself (`modgunn::about`) is always compiled
# (serde_json only) so even the lean build can answer `modgunn about`.
framework = ["dep:jera", "jera/runners", "dep:skidbladnir", "dep:anyhow"]
# The LIVE jera container engine for the Tools-menu demo (routes THROUGH draupnir's
# ONE OCI engine). Heavy (bollard/podman REST); a separate opt-in ON TOP of
# `framework`, exactly as korp's `containers` sits on its default infra. Without it
# the demo `ContainerSpec` still compiles + `to_draupnir().validate()`s (the tested
# wiring); only the live `run_demo_feed` spawn needs this.
framework-oci = ["framework", "jera/container-oci"]
# `testmatrix` — turn the functional surfaces' `functional_status(component,
# check, ok, detail)` markers from a compiled-out `#[inline]` no-op into live
# rows shipped to the nornir test-matrix warehouse. OFF by default so the lean
# `scan` gate (holger / static-holger) pulls no nornir dep; CI / `nornir test`
# opts in. Pulls nornir-testmatrix's own `testmatrix` feature so the emitter
# actually records (`cargo build --features testmatrix`).
testmatrix = ["dep:nornir-testmatrix", "nornir-testmatrix/testmatrix"]
[dependencies]
serde = { version = "1", features = ["derive"] }
# OSV-format advisory import (the lingua-franca SCA feed). Tiny, pure-Rust; rides
# in the lean `scan` build so an offline OSV/GHSA snapshot imports into Advisory.
serde_json = "1"
# ── The ONE shared, egui-FREE app-shell MODEL (edda leaf) — REUSE (LAW #5) ──────
# `nornir_appshell::Identity` is the constellation-wide About-card model korp /
# holger / modgunn each used to hand-roll. modgunn's `about::identity()` builds
# THIS shared shape (no bespoke `struct Identity`), so the shared `facett-about`
# egui card — which renders a `nornir_appshell::Identity` — consumes modgunn's
# About unchanged. serde-only (no egui), so it rides even the lean `scan` build /
# holger / static-holger that link modgunn's lib with ZERO extra weight. Path+
# version sibling consumption (published leaf; path locally, crates.io on release).
nornir-appshell = { path = "../edda/crates/nornir-appshell", version = "0.1", default-features = false }
# ── The SHARED parallel engine (rides in EVERY build, incl. the lean `scan` gate).
# modgunn owns ZERO thread pools of its own: the fleet scan (`AdvisoryEngine::fleet`,
# `warehouse::scan_components`) fans `components × advisories` across all cores via
# znippy-zoomies' no-barrier fork-join gatling (`gatling_forkjoin::gatling_for_each`)
# — NOT rayon. 0.1.15+ is itself rayon-free (pure std scoped threads), so this keeps
# the whole modgunn tree rayon-free per the engine law.
znippy-zoomies = "0.1"
# ── `warehouse` feature only: skade is the embedded-Iceberg seam (NEVER nornir) ──
# The high-level `skade` crate re-exports `arrow_array` / `arrow_schema` / `iceberg`
# / `skade_katalog`, so modgunn needs no direct arrow/iceberg deps. default-features
# = false drops the optional DataFusion `sql` surface (opt in later via skade's
# `sql` feature when richer change-queries are wanted — see the CDC seam).
skade = { version = "0.7.0", path = "../skade/skade", default-features = false, optional = true }
# `warehouse-sql` only: the DataFusion SQL surface skade itself cannot carry — it
# depends on apache/datafusion git (the only build on arrow 59), which makes it
# unpublishable, so it lives in its own crate beside skade (see
# ../skade/skade-sql/FOLDING-BACK.md). `WarehouseSql::sql` is the exact call
# `scan_fleet_sql` used to make on `skade::Warehouse` before the split; korp
# consumes the same crate the same way (korp/src/warehouse/skade_impl.rs).
# Path+version, same class as `nornir-zone` above.
skade-sql = { version = "0.2.0", path = "../skade/skade-sql", optional = true }
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
anyhow = { version = "1", optional = true }
uuid = { version = "1", features = ["v4"], optional = true }
# `warehouse` only: the read-only copied-snapshot open (`Warehouse::open_read_only`)
# copies `catalog.redb` into an auto-cleaned temp dir when the live catalog is
# locked by another writer (a running nornir-server), so a reader never contends.
tempfile = { version = "3", optional = true }
# ── `warehouse` feature only: the ONE shared `Zone` primitive (edda leaf) ──────
# The constellation-wide environment axis (Demo/Test/Prod, default Demo) every
# nordisk product understands — CONSUMED, never twinned (L5). modgunn threads the
# selected zone into its data namespace (`Zone::namespace("modgunn")` = `modgunn_<zone>`)
# so `warehouse import`/`scan`/`stats` land in an isolated per-zone namespace. std-only,
# ZERO runtime deps, `publish = false` → path+version sibling consumption. Gated under
# `warehouse` (that is where namespacing lives + where the CLI bin needs it), so the
# lean `scan` gate (holger / static-holger) never pulls it.
nornir-zone = { path = "../edda/crates/nornir-zone", version = "0.1", optional = true }
# ── `rustsec` feature only: the rustsec advisory-db `.md` front-matter parser ──
# Stays optional so the lean `scan` build (holger's gate, static-holger) never
# pulls a toml parser. nornir's importer enables `rustsec` alongside `warehouse`.
toml = { version = "1", optional = true }
# ── `manifests` feature only: pure-Rust read-only XML tree for pom.xml / .csproj
# dependency extraction. Tiny, zero-unsafe, no_std-friendly; stays optional so the
# lean `scan` build never pulls an XML parser. JSON manifests reuse serde_json and
# TOML ones reuse the `toml` dep above; only Maven/NuGet XML needs this.
roxmltree = { version = "0.21", optional = true }
# ── `framework` feature only: the SHARED common-framework crates ──────────────
# jera = the durable JOB LEDGER + the no-shell CONTAINER runtime (routed THROUGH
# draupnir's ONE OCI engine). `default-features = false` keeps the container/VM
# *engines* out of the lean spec build — the `ContainerSpec` + job-ledger types
# always compile; the LIVE engine rides `framework-oci` (→ `jera/container-oci`).
# The SAME crate korp + nornir consume; modgunn's Tools-menu demo is a jera
# `ContainerSpec` run as a tracked job, not a bespoke docker call or thread (L5).
jera = { path = "../edda/crates/jera", version = "0.2.1", default-features = false, optional = true }
# skidbladnir = the SHIP: modgunn's world-delta gatekeeper service declared as a
# `service::ServiceSpec` driven through the shared `Lifecycle` (the ONE systemd
# renderer + a dedicated `modgunn` system user via `systemd::ensure_system_user`),
# instead of a hand-rolled unit. `default-features = false` stays lean. The SAME
# shared crate korp / nornir / holger declare their services through (L5).
skidbladnir = { path = "../Skidbladnir", version = "0.2.0", default-features = false, optional = true }
# ── `testmatrix` feature only: the introspection-marker emitter ──────────────
# Path dep to the sibling nornir-testmatrix crate (heavy-dep-free: pure std +
# serde). Optional + off by default so the lean `scan` build never pulls a
# nornir dep — the marker wrapper compiles to a no-op without this feature.
nornir-testmatrix = { version = "0.2", default-features = false, optional = true }
# ── `provenance-verify` feature only: the build-provenance seal reader/verifier ──
# The dep-light attestation types + canonical digest (`Attestation`,
# `BuildProvenance`, `canonical_bytes`, `digest_matches_record`) reused verbatim so
# modgunn's verifier consumes the EXACT record shape + canonicalization nornir's
# seal signs — any reimplementation would drift and break the digest. This crate has
# NO nornir/modgunn dependency (it is upstream of both, like znippy-common), so it
# does not recreate the holger<->nornir cycle. Same path-dep class as
# nornir-testmatrix above; optional + off by default keeps the lean scan build clean.
# Depended on by crates.io VERSION only (NO sibling `path`): it is a published nornir
# member crate, so a hardcoded `../nornir/...` path broke any nornir git-worktree whose
# dir name != `nornir` (package-path collision). Version-dep drops that assumption.
nornir-build-thing = { version = "0.2.0", optional = true }
# Opens the znippy-sealed provenance archive (`ZnippyArchive::open` +
# `ZnippyReader::extract_file`) and verifies its detached CMS seal
# (`sign::verify_digest` / `CertStore`) — the pure-Rust RustCrypto verify path, the
# same one nornir's `ZnippySealer` signs with. Heavy (arrow + RustCrypto), so it
# rides ONLY the opt-in `provenance-verify` feature; the lean scan gate never pulls it.
znippy-common = { path = "../znippy/znippy-common", version = "0.10.0", optional = true }
# Bridge the crates.io `nornir-build-thing` version-deps (above) to the LOCAL edda
# checkout. Registry 0.1.3 predates the current API (the `variant::BuildVariant`
# `embeds`/`member`/`shape`/`kind` axes) that `tests/build_variants.rs` exercises, so
# without this patch the test fails `E0560: BuildVariant has no field embeds`. Mirrors
# nornir's own `[patch.crates-io]`. Drop this line once nornir-build-thing 0.1.4
# (current API) is published to crates.io — then modgunn is a plain version-dep,
# independent of where the nornir checkout lives.
#
# WHY a `[patch]` and not a direct `path=` on the dep (the old worktree-collision bug):
# a `[patch]` is OVERRIDABLE — a git worktree of nornir under a differently-named dir
# can repoint it via a `.cargo/config.toml` `[patch.crates-io]` (or by editing this
# path), whereas a hardcoded member `path=` cannot be overridden and collided whenever
# the nornir checkout dir name != `nornir`.
[patch.crates-io]
# 2026-10-05 (merge to main): crates.io serves nornir-testmatrix
# (published 2026-10-04; the siblings carry the same code, at most test/doc edits
# since): their rows are gone.
nornir-build-thing = { path = "../edda/crates/nornir-build-thing" }
# Same rule, for `nornir-testmatrix`. Both edges in this manifest (the optional
# runtime emitter and the dev-only test-matrix wiring proof) are registry deps at
# `version = "0.2"`, which resolves to crates.io 0.2.7 — and 0.2.7 does NOT
# contain the `gatedtests` module at all. `tests/no_silenced_tests.rs` calls
# `nornir_testmatrix::audit_repo`, which lives there and exists only in edda's
# unpublished 0.2.8, so without this bridge that test does not compile.
#
# A `[patch]` and not a `path =` on the deps, for the reasons spelled out above:
# it is overridable per-checkout, it is stripped by `cargo publish`, and it keeps
# the graph on ONE copy of the crate. A path on one edge beside a registry
# version on the other would put two nornir-testmatrix packages in the graph
# whose `TestResultRow` types do not unify.
#
# NB adding a patch does not re-resolve an existing Cargo.lock on its own: cargo
# warns "patch `nornir-testmatrix` was not used in the crate graph" and keeps
# 0.2.7 until `cargo update nornir-testmatrix` is run once.
#
# Drop this line once nornir-testmatrix 0.2.9 (with `gatedtests`) is on crates.io
# — the `version = "0.2"` reqs already accept it.
[dev-dependencies]
# Build-matrix test ONLY (never ships): the `tests/build_variants.rs` harness
# DISCOVERS + VALIDATES `.nornir/build-variants.toml` through `cargo xtask build`'s
# `BuildVariantSet`, regardless of which optional features are on. Same crate /
# version as the optional runtime dep above, so Cargo unifies the one source.
nornir-build-thing = { version = "0.2.0", default-features = false }
# Oracle for the rustsec range-inversion tests ONLY (never ships): the M1
# `patched`/`unaffected` → `RangeEvent` conversion is checked for equivalence
# against cargo-audit's own `!patched && !unaffected` predicate, expressed with the
# real `semver::VersionReq`. Lean build stays semver-free; tests get the true rule.
semver = "1"
# Test-matrix wiring proof ONLY (never ships — a dev-dependency is not propagated
# to consumers, so holger / static-holger stay nornir-dep-free). The
# `tests/testmatrix_wiring.rs` inject-and-assert test drains the functional-status
# rows the surfaces emit under `--features testmatrix` and asserts they land; it is
# `#![cfg(feature = "testmatrix")]` so it compiles to nothing without the feature.
# Same crate/path as the optional runtime dep above, so Cargo unifies the
# `testmatrix` feature onto it and the emitter actually records.
nornir-testmatrix = { version = "0.2", default-features = false }
# The `modgunn` CLI (the secure-side `warehouse import` driver + `stats`). Needs
# the warehouse seam AND the scan parsers, so it builds only with both features on
# (`cargo run --features warehouse`). The lean lib build is unaffected.
[[bin]]
name = "modgunn"
path = "src/main.rs"
required-features = ["warehouse", "scan"]
# HEAVY head-to-head: Engine A (linear) vs Engine B (inverted index) on a big
# synthetic fleet, at full cores + peak RSS reported. Custom harness (no criterion)
# so it measures whole-fleet throughput at max parallelism, not per-call latency.
# Fans out on the SAME gatling engine the production scan uses (no rayon), so the
# bench measures the shipped parallel path, not a throwaway rayon one.
# Run: `cargo bench --features scan --bench engine_ab`.
[[bench]]
name = "engine_ab"
path = "benches/engine_ab.rs"
harness = false
required-features = ["scan"]
# HEAVY multi-ecosystem manifest PARSE throughput: generate a large synthetic
# lockfile per ecosystem (cargo/pip/npm/maven/go/gem) and measure pure-Rust,
# zero-shell parse into the shared SBOM component model — components/sec, host-
# stamped plain numbers. Custom harness (no criterion): whole-manifest throughput.
# Run: `cargo bench --features manifests --bench manifest_parse`.
[[bench]]
name = "manifest_parse"
path = "benches/manifest_parse.rs"
harness = false
required-features = ["manifests"]
# Owner, 2026-09-05: test artifacts carry line tables only — full DWARF in every test
# binary and its deps was the bulk of a target dir (gunnar/target 271 GB, korp/target 852 GB
# on the day the disks filled); backtraces still show file:line.
[profile.test]
debug = "line-tables-only"