bench/Cargo.toml
# gunnar's benchmark harness: a **detached** crate with its own `[workspace]`.
#
# ## Why here and not `xtask/examples/` (a deliberate deviation from the proposal)
#
# The original harness proposal suggested `xtask/examples/nornir-bench.rs`,
# because gunnar already has an `xtask` member. That is the wrong home here, for two
# reasons that are recorded rather than re-argued:
#
# 1. `xtask/Cargo.toml` declares **zero dependencies, deliberately**. It is the
# dependency guard, and a guard that grows a dependency tree becomes the thing it
# guards. A workspace member also takes part in resolver-2 feature unification, so
# a bench dev-dependency there can turn features on inside the appliance build.
# 2. `xtask` is a workspace member, so its dev-dependencies are built by
# `cargo test --workspace`, which would drag the whole harness tree into gunnar's
# 517-test run.
#
# `bench/examples/nornir-bench.rs` is the **third accepted location** the house
# bencher contract names ("detached `bench/` crate with its own `[workspace]`;
# `run_bench_example` runs cargo *inside* `bench/`"), and it exists for exactly this
# case. The root `Cargo.toml` is untouched: `[workspace] members` does not list
# `bench`, and this manifest's own `[workspace]` table makes it a separate workspace
# root, so the parent workspace cannot claim it either.
#
# ## Why `nornir-bench` and not `nornir`
#
# That contract's own line says `nornir = { path = "../nornir" }`. Depending on the
# leaf harness crate instead is strictly better here and is what `ordning/bench` and
# `edda/bench` already do:
#
# * `nornir` re-exports `nornir_bench` verbatim (`nornir/src/lib.rs:159`), so the API
# (`Bencher`, `register_bench!`, `run_main_json`, `preview_run`) is identical.
# * `inventory` collects registrations at **link** time. Linking `nornir` would pull
# in every `register_bench!` in `nornir/src/bench_registry.rs`, the day-one trap
# already seen (2026-07-22: `nornir bench run skade` ran 213 `nornir.mimir.*` arms
# and reached zero `skade.*` arms). Linking only `nornir-bench` means the linked
# arm set IS gunnar's arm set. `NORNIR_BENCH_ONLY=gunnar.` is still set from
# `main()` as belt-and-braces, because a future dependency could reintroduce the
# hazard silently.
# * `nornir` is a very large crate (embedded holger, ort/CUDA, egui viz). Building it
# to run a deflate microbenchmark is minutes of build for no measurement.
[package]
name = "gunnar-bench"
version = "0.0.0"
edition = "2021"
publish = false
[workspace]
[dependencies]
# Deflating fixture pack entries. The fixtures build WHOLE objects, so this is
# the only compression the harness itself performs.
flate2 = "1"
# The project-agnostic harness: `register_bench!` + `inventory` + the stdout-JSON
# `BenchRun` envelope + the CPU/mem telemetry sampler (which is where
# `telem_proc_cores_max`, the `core_saturation` input, comes from — task-clock ÷
# wall out of `/proc/self/stat`). NOT `telem_cores_busy_max`, which this comment
# named until 2026-08-03: that one is `/proc/stat`, the WHOLE HOST, and on the desktop
# it reported `gunnar.walk_1m` — a strictly single-threaded arm — as 32/32 cores
# busy while the process held 0.56 of one core, so the gate reading it passed
# unconditionally. `gunnar.clone_concurrent_64` really occupies 10.99–12.11 of 32.
nornir-bench = { version = "0.1.8" }
# ── `nornir-xtask-support` — a crates.io VERSION, never a path ────────────────
#
# `expand_home` lives here and `src/lib.rs` re-exports it. Two rules, both
# measured the hard way on 2026-09-10:
#
# * NEVER `path = "../../edda/…"`. **gunnar must build from ONE clone** — a
# `../edda` path makes a sibling checkout of another repository mandatory for
# `cargo check --manifest-path bench/Cargo.toml`, and it compiled
# `nornir-git v0.1.9` TWICE (registry through `nornir-bench`, path through
# this) — two packages that look like one, `gunnar-pack 1.1.1`-twice again.
# * The version must EXIST. 0.1.1 was committed in edda (`55984e1`) on
# 2026-09-10 but not published until 2026-09-12 (`dwarves crate-release`,
# Gate B rehearsed); in between this line resolved for nobody and the body
# was carried as a cited copy in `src/lib.rs`. 0.1.0 predates `expand_home`.
nornir-xtask-support = { version = "0.1.1" }
# The gunnar crates under measurement.
#
# The edda leaves are crates.io VERSIONS since 2026-09-09, and the reason is not
# tidiness: `gunnar-wire` and `gunnar-store` below reach the SAME crates through
# the root workspace, which now resolves them from the registry. A `path` here and
# a `version` there are TWO DIFFERENT PACKAGES to cargo — both compiled, types
# from one not assignable to types from the other — and the lock proved it, listing
# `gunnar-pack 1.1.1` twice with two sources the moment the root workspace moved.
# That is the shape the root `Cargo.toml` records as dwarves b81545c's
# `E0609: no field dsse`. One spelling per crate, and it is the registry's.
gunnar-pack = { version = "3.0.0" }
# The EWAH / `.bitmap` reader. Donation-shaped — no gunnar dependencies, gix's own
# conventions — so it names no gunnar dependency of its own; the edge runs inward,
# from the bench to it, which is the direction that shape permits.
gunnar-bitmap = { version = "3.0.0" }
gunnar-pktline = { version = "1.1.0" }
# `stages` ON. This is the ONLY thing in the tree that asks for it: the feature
# adds `gunnar-stage` and eighteen stage boundaries to the serve path, and the
# default build — every profile the dependency guard witnesses — compiles neither. See
# `crates/gunnar-wire/src/stage.rs`.
gunnar-wire = { path = "../crates/gunnar-wire" }
gunnar-stage = { version = "1.1.0" }
# `fs` is the on-disk backend (gix-odb loose objects + packs, gix-ref). The
# lookup/repack arms measure THAT backend: the missing batch-lookup seam and O(packs)
# lookup on a miss are pack-index questions a `MemObjectStore` (a `HashMap`) cannot answer.
#
# `RepoStore` and the re-exported `git_storage_trait` — the CONTRACT, not an
# implementation of it. `gunnar-store` names no engine of its own since the
# 2026-08-10 demolition (three of its six traits died; `RepoStore`, `SecretStore`
# and the conformance `Harness` live), so this edge brings no backend with it.
#
# `features = ["conformance"]` for `mem::MemGitServe`, the in-memory control the
# contract's own conformance suite is self-tested against. It is the ONLY
# implementation of `GitServe` this workspace can name: the Arrow arm lives in
# znippy and the gix arm in edda, and depending on either would put an engine
# crate back in this manifest — the thing the block below exists to forbid.
#
# Turning it on here is safe in the one way that matters: `bench/` is a detached
# workspace with its own lockfile, so resolver-2 feature unification cannot carry
# `conformance` (and its `tempfile`/`flate2`/`gix-object` dependencies) into the
# appliance build. That is the whole reason `bench/` is detached.
#
# It is used ONLY for fixtures that are about the wire, the pack format or the
# walker — arms where the store is a place to put objects rather than the subject.
# An arm whose subject IS the store must go through `src/engine.rs`, and
# substituting this in-memory control there would publish a full table about a
# `HashMap`. `engine::open_repo` bails for exactly that reason; do not "fix" it
# with this.
gunnar-store = { path = "../crates/gunnar-store", features = ["conformance"] }
# The population arms (`gunnar.population_read` / `gunnar.population_write`)
# provision 100–100 000 users through `AuthTable::bulk_add_users` in-process:
# one CLI spawn per user is ~5 processes × 100k at the top rung, which is the
# provisioning being the benchmark instead of the server. The control plane is
# not behind the engine seam — `gunnar-server` (already linked above) owns it.
gunnar-core = { path = "../crates/gunnar-core" }
# The admission gate itself, for `admission_log`'s red proof against the REAL
# counter: the test drives a live `gunnar_ssh::Admission` to a binding
# ceiling and asserts the parsed column equals `AdmissionStats::refused()` /
# `peak_in_flight`, not a fabricated 0. `gunnar-server` (linked above) already
# pulls this crate in, so the direct edge adds no build — only the name.
gunnar-ssh = { path = "../crates/gunnar-ssh" }
# ── NO ENGINE CRATE IS DECLARED HERE, and that is the point ─────────────────
#
# a path dependency on the deleted engine crate used to be the next
# line. It was plain and non-optional, and the old znippy engine crate path-depends on
# `znippy-common`, `znippy-compress`, `znippy-plugin-git` and `znippy-zoomies`.
# Cargo reads a path dependency's manifest at RESOLVE time, so **a sibling znippy
# checkout was a hard requirement of building every one of the 49 arms** —
# `pack_deflate`, `pack_trailer_hash`, `capabilities` and `oid_index` included,
# none of which touch a store.
#
# That is precisely the cost `examples/nornir-bench.rs`'s store-seam comment says
# the design exists to avoid, and the comment's closing line — *"bench/ therefore
# stays znippy-free: it knows a path, not a crate"* — was therefore false from the
# day it was written until 2026-08-10.
#
# It is true now. The engine reaches a measurement as an executable and nothing
# else: `GUNNAR_BENCH_GUNNAR_SERVER` for the `ext::` arms,
# `GUNNAR_BENCH_FORGE_BINARY` for the containerised `vs_forge_*` arms. Two seams,
# not one — see `src/engine.rs`, which is the only place they are written down
# together.
#
# **Do not re-add an engine crate here.** If an arm needs an in-process store,
# that is a `GitServe` question, and the answer goes
# behind `src/engine.rs::open_repo` where every arm picks it up at once.
#
# ── AND THAT IS WHAT HAPPENED — 2026-08-11 ──────────────────────────────────
#
# `GitServe` landed and has TWO implementations. `gunnar-server`'s `storage`
# module is the one place in gunnar that names either of them, it holds both
# behind `engine-znippy` / `engine-gix`, and its `Engine::open_repo` hands back
# an `Arc<OpenRepo>` — the only implementor of `RepoStore` in gunnar, i.e.
# exactly the `Box<dyn RepoStore>` shape `engine::open_repo` was written to
# return one day.
#
# So the line below is **not** the thing the paragraph above forbids. It is not
# an engine crate: it is the crate that owns the two-engine seam, and naming it
# is how `bench/` reaches BOTH engines through one trait instead of picking one.
# The 15 arms that ask a store directly — the multi-pack and `.idx` probes,
# `stages`, `have_lookup`, `readside`, and the three `ScaleDaemon` arms — come
# back with it, and they come back as TWO-ENGINE arms, because
# `Engine::selected()` reads the same `GUNNAR_ENGINE` the other two seams read.
# Before this, that variable reached seam 1 and seam 2 and nothing in-process.
#
# The cost the paragraph above is protecting against is a sibling **znippy**
# checkout being required to build all 49 arms. That cost is real and it is
# already paid everywhere else: `gunnar-server` names `znippy-plugin-git` and
# `storage-git-gix` by path (a C-toolchain build cost its manifest records paying), so
# building gunnar at all already needs `../znippy` and `../edda`. `bench/` links
# the gunnar binaries' own workspace; it does not add a requirement, it inherits
# one that the arms already depend on to have a subject to measure.
gunnar-server = { path = "../crates/gunnar-server", features = [
"engine-znippy",
"engine-gix",
] }
# The install arms (`gunnar.install_container` / `gunnar.install_kvm`) drive the
# SAME code `cargo xtask install-test` drives, rather than re-timing their own copy
# of the workflow. `installtest/` is another detached workspace, so this is a plain
# relative path and neither lockfile touches the other.
gunnar-installtest = { path = "../installtest" }
# gunnar's OWN git client — the half nothing had stressed.
#
# The `vs_git_*` arms above make **stock git** the client on both sides, which is
# what makes their ratios statements about the two SERVERS. That deliberately
# leaves gunnar's client entirely unmeasured: `gunnar-client` drives clone/fetch
# through `nornir-git` (protocol-v2 `ls-refs`, sha256 oids) and push through its
# own `send_pack`, and no bench arm had ever run either. The `real_repo_*` arms
# below are the other half — our client against our server, on real repositories,
# with **stock git as the judge** of every object graph either side produces.
gunnar-client = { path = "../crates/gunnar-client" }
# `gix::open`, `gix::hash::Kind`. The version and the default-features-off shape
# are the parent workspace's, so the resolver unifies rather than building gix
# twice; the extra features gunnar-client asks for arrive through it.
gix = { version = "0.90", default-features = false, features = [
"max-performance",
"revision",
"blob-diff",
"sha1",
"sha256",
] }
# The container engine the `vs_forge_*` arms boot gitea, Forgejo and gunnar
# with. `backend-oci` is bollard over podman's REST socket, with **no shell
# fallback at all**: an unreachable socket is `Error::Backend`, never a pretend
# boot. That is the whole reason it is here rather than dwarves' `RealRunner`,
# which routes container steps to `DemoRunner` and returns `ok("would run …")`
# having executed nothing — a benchmark on top of that produces a full table of
# numbers with no server behind it.
#
# Default features off: draupnir's KVM (tunnr) and Redfish backends are not
# wanted, and `default = []` is already pure-std, so this only adds bollard,
# tokio and their http stack. `bench/` is a detached workspace with its own
# lockfile, so none of it reaches gunnar's own tree.
#
# A crates.io VERSION since 2026-09-09, and it was the LAST sibling path here.
# draupnir path-depends on edda and znippy-zoomies itself, so while this line said
# `path` it dragged eight crates in twice — MEASURED off this lockfile:
# nornir-build-thing 0.1.14, nornir-git 0.1.9, nornir-hash 0.1.0, rotaryengine
# 0.1.12, wg-appliance-core 0.1.4, znippy-common 0.9.15, znippy-zoomies 0.1.25 and
# ldeflate 0.1.2 each appeared as a PATH package and a registry package at the SAME
# version. Same version, two packages, both compiled, types not interchangeable.
draupnir = { version = "0.2.0", default-features = false, features = ["backend-oci"] }
# The ragnar static search tree (`stree`/`stree32`), for `gunnar.oid_index`.
#
# A DEV-ONLY edge and deliberately only that: the arm asks whether an S+tree
# beats git's `.idx` fanout+bsearch at oid -> pack offset, and the answer has to
# be a measurement before anything in `crates/` may name this crate. `bench/` is
# a detached workspace with its own lockfile, so nothing here reaches gunnar's
# tree or the appliance's crate count. `default-features = false` keeps the
# optional `testmatrix`/`vann-mmap` features off.
znippy-zoomies = { version = "0.1.26", default-features = false }
# The `.idx` is read the way git reads one: mapped, not slurped. `gunnar.oid_index`
# times a probe against a 40 MB index, and a `read_to_end` of it would put the
# read in the bracket and the page cache out of it.
memmap2 = "0.9"
# `gunnar.chain_*` / `deltachain-probe` open the SAME `gix-odb` the `fs` backend
# opens, twice: once with the pack cache gunnar has today (none — gix-odb's
# `new_pack_cache` defaults to `None`) and once with a 64 MiB LRU. The two
# handles differ in that one call and nothing else, which is what makes the
# ratio a statement about the cache rather than about two code paths.
gix-odb = { version = "0.87", features = ["sha1", "sha256"] }
gix-hash = { version = "0.28", features = ["sha1", "sha256"] }
# Two arms want this, and their needs union rather than conflict:
#
# * `index::File`, for the `SCALE_IDX` arm of `fanout16-ab` — the 352 396- and
# 1 000 000-object fixtures have no `.bitmap`, so the seam has to be reached
# without `PackBitmap`;
# * `cache::lru::MemoryCappedHashmap`, which `deltachain-probe` holds
# `gunnar_store::delta_cache::DeltaBaseCache` against. gunnar itself does NOT
# take this feature — it pulls `clru` and four more crates and moved the
# appliance guard 280 -> 283 — which is exactly why the comparison has to
# happen in `bench/`, a detached workspace whose lockfile reaches nothing.
#
# Same version the parent workspace resolves, so nothing is built twice.
gix-pack = { version = "0.77", default-features = false, features = [
"pack-cache-lru-dynamic",
] }
gix-object = "0.66"
anyhow = "1"
serde_json = "1"
# `wait4(2)`, and nothing else. The head-to-head arms measure a *server process*
# (stock `git-upload-pack`, or gunnar's wire crate hosted in a re-exec of this
# binary). The four numbers the comparison needs are wall clock, bytes, peak RSS
# and CPU seconds, and they cannot all be had by polling `/proc`: a process that
# exits between two polls takes its `VmHWM` and its `utime` with it. `wait4`
# returns the `rusage` of the reaped child atomically at exit, and on Linux that
# figure is `RUSAGE_BOTH`, the child's own usage *plus* its already-reaped
# descendants', so `git upload-pack`'s fan-out into `git pack-objects` is
# counted, which is
# exactly the "wins by burning four cores" case wall clock alone hides.
libc = "0.2"
[features]
# `stages` ON by default, and this manifest is the ONLY thing in the tree that
# turns `gunnar-wire/stages` on: the feature adds `gunnar-stage` and eighteen
# stage boundaries to the serve path, and every profile the dependency guard witnesses
# compiles neither.
#
# It is a *feature* rather than a hard `features = ["stages"]` edge so that
# `--no-default-features` gives a build of this same harness with the probes
# **compiled out**. That build is the other half of two proofs:
#
# * `gunnar.stage_overhead` — what the instrumentation costs, measured rather
# than asserted;
# * `stage-probe wirehash` — that the instrumented server puts the identical
# bytes on the wire. Comparing recording against not-recording inside one
# binary cannot see a probe that perturbed the code generation around it;
# comparing two builds can.
default = ["stages", "gunnar-fifo"]
stages = ["gunnar-wire/stages"]
# `deltachain-probe`'s FIFO-against-LRU column, which is the ONLY thing in that
# 555-line probe that names a gunnar crate
# (`gunnar_store::delta_cache::DeltaBaseCache`). ON here, so this build prints
# the column it always printed; OFF in `bench-stock/Cargo.toml`, which builds
# the same source file with no gunnar crate in its graph at all. That manifest
# exists because the eighteen gunnar crates are parked under
# `parked-record/crates/` and `bench/` therefore does not resolve.
gunnar-fifo = []
# The counting `#[global_allocator]`, OFF by default.
#
# A `#[global_allocator]` is the whole executable's, so switching one on in
# `nornir-bench` puts a branch in front of every allocation in six other lanes'
# arms, whose rows land in the same `bench_runs` table. Default-off means a
# plain `nornir bench run gunnar` is unperturbed; the allocation columns come
# from `NORNIR_BENCH_FEATURES=count-alloc nornir bench run gunnar`, which is
# still `nornir bench run` and still persists.
#
# `examples/stage-probe.rs` installs it unconditionally, because it measures
# nothing else.
count-alloc = []
[[example]]
name = "nornir-bench"
path = "examples/nornir-bench.rs"
# ── the seven declarations that outlived their source, 2026-08-08 ───────────
#
# `222d9a1` ("Amputate the gix-only bench probes") moved nine probe sources out
# to `parked-record-2/bench-gix/` — three minutes after `a38a236` removed
# `gunnar-store`'s `fs` backend — and left SEVEN `[[example]]` declarations here
# pointing at files that no longer exist: `fanout16-ab`, `packwrite-probe`,
# `unselbase-probe`, `transportbuf-probe`, `walkbudget-probe`, `gix-census`,
# `fetchbytes-probe`.
#
# That is a manifest error, not a compile error: cargo answers it with seven
# `can't find example ... at path` lines and refuses to build **any** target in
# this workspace, including the 49 arms. It survived two days and six commits
# unseen, and no report of the tree named it — the two surveys that looked at
# `bench/` were both stopped one step earlier, at a `gunnar-store` feature that
# does not resolve, so cargo never reached target resolution to complain.
#
# Removed here. FOUND BY `cargo xtask bench-build`, on its first run.
# TEMPORARY, not for merge: counts every oid -> pack-offset resolution a
# request makes, at the seams gunnar owns. Its own target for the reason
# `readside-probe` is: a count is a diagnostic, not a row worth persisting.
[[example]]
name = "oidlookup-probe"
path = "examples/oidlookup-probe.rs"
# The delta-chain lane's probe: read/header/entry-copy cost per depth on a real
# pack, plus the delta-base-cache size sweep. Its own target because it takes a
# corpus path on argv and prints a table, which is a diagnostic shape and not a
# `BenchRun`.
[[example]]
name = "deltachain-probe"
path = "examples/deltachain-probe.rs"
# The same lane's push-verification half: `index_pack` over a ladder of packs
# holding one object set at seven delta depths.
[[example]]
name = "indexdepth-probe"
path = "examples/indexdepth-probe.rs"
# One real push, decomposed into stages. Reuses `gunnar_bench::stages::push`
# rather than rebuilding a push driver, so it and `gunnar.push_stages` are
# decomposing the same code.
[[example]]
name = "pushstage-probe"
path = "examples/pushstage-probe.rs"
[[example]]
name = "readside-probe"
path = "examples/readside-probe.rs"
# `ls-refs` cost against ref count, after tag re-peeling. A diagnostic table, its own
# target for the reason `readside-probe` is; the persisted arm is
# `gunnar.advertise_refcount` and both share `src/advertise.rs`.
[[example]]
name = "advertise-probe"
path = "examples/advertise-probe.rs"
# Symbols, not codegen. `gunnar.cpu_phase` attributes server CPU to phases by
# resolving `perf` samples to function names, and a stripped release build
# resolves every one of them to the same unknown address — which comes out as a
# tidy "100% other" and looks like a measurement. `debug = 1` adds line tables
# and leaves optimisation exactly where it was, so every other arm in this
# binary measures the same code it measured before.
[profile.release]
debug = 1
# The storage-path counting probe: what one ref transaction costs on disk and in
# fsync, and what an open costs to replay. A diagnostic, not a persisted row, so
# it is its own target for the same reason `readside-probe` is.
[[example]]
name = "stomach-probe"
path = "examples/stomach-probe.rs"
# The stage decomposition as a diagnostic: the whole table, every column, for
# one repository, printed. Its own target for the reason `readside-probe` is —
# a table is something a human reads once, not a row worth persisting — and,
# specifically, because it installs a counting `#[global_allocator]` that has no
# business being in the binary the other arms run in.
[[example]]
name = "stage-probe"
path = "examples/stage-probe.rs"
# ── the one patched dependency, restated because cargo cannot inherit it ─────
#
# `bench/` is a DETACHED workspace (see the `[workspace]` table above), and
# `[patch]` is a property of a workspace ROOT that cargo does not inherit through
# a path dependency. Without this table every arm here would measure the
# *unpatched* `gix-features` while the shipped binary ran the patched one — a
# benchmark of code that is not the code, which is the exact shape of blind
# result this tree keeps finding. See the parent `Cargo.toml` for what the patch
# is and `vendor/gix-features/PATCH.md` for the diff.
#
# Delete this together with the parent's the day the fix lands upstream.
[patch.crates-io]
# No `package =`: it renames the crate the patch is FOR, so it silently unbound
# this table between 2026-08-23 and 2026-08-25. See the parent `Cargo.toml`.
gix-parallel = { path = "../vendor/gix-parallel" }
# TEMPORARY (release-3 merge, 2026-10-03), the same table the root carries: this
# detached workspace reaches gunnar's crates by path, and they ride edda's
# gix-0.88 / russh-0.63 crates until edda publishes them. Delete with the root's.
gunnar-pack = { path = "../../edda/crates/gunnar-pack" }
gunnar-delta = { path = "../../edda/crates/gunnar-delta" }
gunnar-bitmap = { path = "../../edda/crates/gunnar-bitmap" }
gunnar-sendpack = { path = "../../edda/crates/gunnar-sendpack" }
gunnar-stage = { path = "../../edda/crates/gunnar-stage" }
gunnar-pktline = { path = "../../edda/crates/gunnar-pktline" }
nornir-git = { path = "../../edda/crates/nornir-git" }
nornir-build-thing = { path = "../../edda/crates/nornir-build-thing" }
storage-git-gix = { path = "../../edda/crates/storage-git-gix" }
git-storage-trait = { path = "../../znippy/git-storage-trait" }
# The estate's sibling patches this workspace's lock takes that the root's old
# `.cargo/siblings.toml` include used to supply (G-B7, 2026-10-26: the root takes
# none now, so every estate workspace names its own; xtask/tests/lone_clone.rs).
estate-clock = { path = "../../edda/crates/estate-clock" }
disk-snapshot = { path = "../../edda/crates/disk-snapshot" }
appliance-pid1 = { path = "../../edda/crates/appliance-pid1" }
wg-appliance-core = { path = "../../edda/crates/wg-appliance-core" }
edda-admit = { path = "../../edda/crates/admit" }
nornir-auth-oidc = { path = "../../edda/crates/nornir-auth-oidc" }
nornir-auth-saml = { path = "../../edda/crates/nornir-auth-saml" }
nornir-auth-webauthn = { path = "../../edda/crates/nornir-auth-webauthn" }
nornir-identity = { path = "../../edda/crates/nornir-identity" }
nornir-identity-session = { path = "../../edda/crates/nornir-identity-session" }
nornir-identity-sso = { path = "../../edda/crates/nornir-identity-sso" }
nornir-identity-directory = { path = "../../edda/crates/nornir-identity-directory" }
nornir-auth-sshsig = { path = "../../edda/crates/nornir-auth-sshsig" }
nornir-service = { path = "../../edda/crates/nornir-service" }
nordisk-pairing = { path = "../../Skidbladnir/crates/installer/nordisk-pairing" }
korp-installer = { path = "../../tunnr/crates/korp-installer" }
[dev-dependencies]
tempfile = "3"
# Minting the CA, server and client certificates the mTLS arms need. A
# DEV-dependency: certificates are test material, and a crate that can mint a CA
# has no business in a shipped server graph, or in its dependency budget.
cert-helper = "0.5.1"