server/lib/Cargo.toml
[package]
name = "holger-server-lib"
# 0.6.10: local source DRIFTED from the published 0.6.9 under the same version number.
# crates.io bytes are immutable, so the edit can only reach consumers as a new
# version. Bumped 2026-08-23 by the release cascade: Gate B refused nornir 0.5.5
# because the published copies of this repo are stale.
version = "3.1.1"
edition = "2021"
authors = ["Ignalina ApS", "Vetra Automation AB"]
description = "Holger server library: config, wiring, gRPC service, Rust API"
license = "AGPL-3.0-only"
repository = "https://git.gunnar.rs/nordisk/holger"
# Development notes, agent instructions and machine-local tooling stay in the
# repository; they are not part of the published crate.
exclude = [
".nornir/",
]
# Canonical importable crate name. Dependents alias the package
# (`package = "holger-server-lib"`) and the crate's own integration tests under
# `tests/*.rs` import `holger_server_lib::` — one Rust path everywhere.
[lib]
name = "holger_server_lib"
[dependencies]
# `rustix` — THE FREE-SPACE SOURCE, and the one runtime dependency this ruling
# adds (?18b / the storage meters, answered 2026-09-20).
#
# `storage_total_bytes`, `storage_servable_bytes` and `storage_used_bytes`
# answered `Refused(NO_FILESYSTEM_SOURCE)` because holger had no way to call
# `statvfs(3)`. It does now, and this is it.
#
# WHY `rustix` AND NOT `libc`. Both were already in Cargo.lock through other
# crates, so neither adds a new name to the tree and the choice is about what the
# call site looks like. `rustix::fs::statvfs(path) -> io::Result<StatVfs>` is safe
# Rust: no `unsafe` block, no zeroed `MaybeUninit` struct, no errno fishing — and
# on Linux it goes through rustix's `linux_raw` backend, so it issues the syscall
# directly and pulls no C library in at all, which is what the pure-Rust law
# actually asks for. `libc` would have put an `unsafe` block and a
# zero-initialised struct in the middle of a metering path whose whole reason to
# exist is that a zero must never be mistaken for a measurement. rustix is also
# what nornir-service's neighbours already reach for.
#
# `default-features = false` + `fs`: the filesystem calls and nothing else. No
# net, no process, no termios, no std-feature sprawl into a server binary that
# wants one syscall.
rustix = { version = "1.1", default-features = false, features = ["fs", "std"] }
# The FROZEN `holger-<subsystem>-<n>` application error codes (zero-dep leaf).
# Floor 0.1.0 -> 0.1.1: 0.1.0 is what the release gate reports as missing the
# AUDIT_*/AUTH_*/PROMOTE_*/SERVER_* codes. It is NOT actually missing them — they
# are declared inside the `wired` macro, so a source-text scan of the published
# tarball cannot see them — but 0.1.1 is the version this repo now ships anyway.
holger-errcode = { version = "3.1.1", path = "../../errcode" }
# `derive(Error)` for the public error enums (lane C3): the Display text stays
# what it was, and a wrapped io/ladder error is now a real `source()`. Already in
# Cargo.lock through tonic, rustls and others, so this adds no new crate.
thiserror = "2"
# The audit log's Arrow IPC INDEX (lane HOL-AUDIT, `src/audit/arrow_ipc.rs`): the
# searchable, columnar copy of the hash-chained JSON lines, rebuilt from them. No new
# crate: arrow 59 is in every build already through znippy-common (holger-traits).
arrow-array = "59"
arrow-ipc = "59"
arrow-schema = "59"
traits = { package = "holger-traits", version = "3.0.0", path = "../../traits" }
# holger.rs's PUBLIC FRONT PAGE. A leaf with two dependencies, both already
# here: the page, the three-column release row and the whole door are one pure
# function there, so the HTTP gateway's job is four lines of mounting and the
# page itself is tested in a crate that builds in seconds. It ranks nothing —
# `retention::cmp_version` is passed in, so there is one version ordering in
# this server and not two.
#
# OPTIONAL since 2026-09-17, behind the `front` feature. It was unconditional,
# which meant the PUBLIC WEB PAGE was compiled into the airgap appliance — the
# one artifact that is ISO-installed, has no shell and serves no browser. The
# page is a leaf that is mounted; whether this server is the thing that mounts
# it is a build decision, and now it is spelled as one. See `[features] front`.
holger-front = { version = "3.0.0", path = "../../holger-front", optional = true }
repository-file-rust = { package = "holger-rust-file-repository", version = "3.0.0", path = "../repository/file-rust", optional = true }
repository-znippy-rust = { package = "holger-rust-znippy-repository", version = "3.0.0", path = "../repository/znippy-rust", optional = true }
repository-znippy-maven = { package = "holger-znippy-maven-repository", version = "3.0.0", path = "../repository/znippy-maven", optional = true }
repository-znippy-python = { package = "holger-znippy-python-repository", version = "3.0.0", path = "../repository/znippy-python", optional = true }
repository-znippy-npm = { package = "holger-znippy-npm-repository", version = "3.0.0", path = "../repository/znippy-npm", optional = true }
repository-znippy-nuget = { package = "holger-znippy-nuget-repository", version = "3.1.0", path = "../repository/znippy-nuget", optional = true }
repository-znippy-go = { package = "holger-znippy-go-repository", version = "3.0.0", path = "../repository/znippy-go", optional = true }
repository-znippy-gem = { package = "holger-znippy-gem-repository", version = "3.0.0", path = "../repository/znippy-gem", optional = true }
repository-znippy-deb = { package = "holger-znippy-deb-repository", version = "3.1.0", path = "../repository/znippy-deb", optional = true }
repository-znippy-rpm = { package = "holger-znippy-rpm-repository", version = "3.1.0", path = "../repository/znippy-rpm", optional = true }
repository-znippy-helm = { package = "holger-znippy-helm-repository", version = "3.1.0", path = "../repository/znippy-helm", optional = true }
repository-file-oci = { package = "holger-file-oci-repository", version = "3.0.0", path = "../repository/file-oci", optional = true }
repository-znippy-docker = { package = "holger-znippy-docker-repository", version = "3.0.0", path = "../repository/znippy-docker", optional = true }
repository-znippy-conda = { package = "holger-znippy-conda-repository", version = "3.0.0", path = "../repository/znippy-conda", optional = true }
repository-znippy-composer = { package = "holger-znippy-composer-repository", version = "3.1.0", path = "../repository/znippy-composer", optional = true }
repository-znippy-package = { package = "holger-znippy-package-repository", version = "3.0.0", path = "../repository/znippy-package", optional = true }
# The BUILD HISTORY backend: not another ecosystem mimicked over a znippy store,
# but the forge's provenance log — one attestation per built artifact, addressed
# by the artifact's own sha256. Read-only by design; see its crate docs.
repository-znippy-build-thing = { package = "holger-znippy-build-thing-repository", version = "3.0.0", path = "../repository/znippy-build-thing", optional = true }
# ── The package-handler catalog: which HANDLER reads a repository's container ──
# The seventeen backends above are what `ron_repo_type` selects. Since stinky 1.4
# (2026-09-21) every one is OPTIONAL and reached through a `fmt-*` feature below —
# a tier links the ecosystems its row NAMES, and nothing else. `ron_handler`
# selects one level down — the reader — and the catalog is its lookup. Kept a
# plain dependency (no wasmtime by default) so every tier can name a LINKED
# handler; the `wasm-plugins` feature adds the loader that reads a directory of
# `.wasm`, which is what makes a new handler a config string plus a file rather
# than an edit to this manifest.
holger-plugin-host = { version = "3.0.0", path = "../../plugin/host" }
# The open meter guest, LINKED (`allow-meter`, H2 #2): the open rows' meter with no wasm runtime.
holger-plugin-allocation = { version = "3.0.0", path = "../../plugin/guest-allocation", optional = true }
# The estate board (lane E3) is DATA (Holger v2.0 H2 #1): `AdminService.Topology` and
# `GET /-/estate.json` serve its rows; holger-ui draws them. No server build links a
# renderer (`holger-recipe/tests/every_shape_stays_slim.rs`).
# The two handlers holger ships compiled in. Both are `holger-plugin-abi` +
# `holger-handler-bundle-core` plus a `default-features = false` classifier from
# znippy — no arrow, no io_uring, no gatling — so they cost the airgap tiers
# nothing. They register under the names their OWN manifests declare, which is
# what lets one `ron_handler` string resolve to either a linked handler or a
# loaded module.
holger-handler-skidbladnir = { version = "3.0.0", path = "../../plugin/handler-skidbladnir" }
holger-handler-rust-toolchain = { version = "3.0.0", path = "../../plugin/handler-rust-toolchain" }
tokio = { version = "1.54.1", features = ["full"] }
serde = { version = "1.0.229", features = ["derive"] }
anyhow = "1.0.104"
ron = "0.12"
async-trait = "0.1.92"
# Already in Cargo.lock via tonic/tokio-stream — a direct edge, NOT a new package.
futures-util = { version = "0.3", default-features = false, features = ["std", "async-await"] }
tonic = { version = "0.14", features = ["tls-ring"] }
prost = "0.14"
tonic-prost = "0.14"
hyper = { version = "1", features = ["server", "http1", "http2"] }
hyper-util = { version = "0.1", features = ["server", "server-auto", "tokio"] }
http-body-util = "0.1"
# The gRPC door's `X-Holger-Domain` layer (lane HOL-CDS, `boundary::DomainLayer`); in the
# lock already (tower, tonic).
tower-layer = "0.3"
log = "0.4"
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
x509-parser = { version = "0.18", features = ["verify"] }
tokio-stream = "0.1"
# The gRPC door's TLS 1.3 serve (`exposed::tls::serve_grpc_tls13`, stinky SB #39):
# the gRPC-web layer nornir-service's tail applies, applied on holger's own
# incoming. Already in Cargo.lock through nornir-service — a direct edge.
tonic-web = "0.14"
# OPTIONAL + feature-gated behind `online` (which `default` turns on). The ONLY
# outbound-HTTP client in the core: it serves the ONLINE OIDC paths in `auth.rs`
# (`/userinfo` for opaque tokens + the `jwks_uri` JWKS fetch). The airgap `mini`
# tier omits `online`, so a `--no-default-features` / `mini` build links NO
# reqwest at all — the offline JWT-against-cached/inline/file-JWKS path (pure
# `jsonwebtoken`, zero egress) is the only auth path compiled. Verify with
# `cargo tree --no-default-features -e no-dev | rg reqwest` (must be empty).
reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "json", "form"], optional = true }
# reqwest 0.13 has no bundled roots and its `rustls` feature means aws-lc-rs (C) + the
# OS trust store; ring + the Mozilla bundle keep 0.12 `rustls-tls` behaviour (see the
# `tls_roots` helpers). 2026-10-02.
# ureq: SYNC, runtime-agnostic HTTP client for the remote-registry proxy upstream
# (RustRegistryUpstream). reqwest is async and panics if blocked-on inside the
# hyper handler; ureq's blocking GET is safe from the handler AND from tests.
# OPTIONAL + behind `online`: it is an OUTBOUND HTTP client, so the zero-egress
# airgap `embedded`/`mini` tiers (which omit `online`) must NOT link it. Leaving it
# non-optional linked a live HTTP client into the airgap binaries — same hole
# reqwest had. Verify: `cargo tree --no-default-features --features embedded | rg ureq`
# (must be empty).
ureq = { version = "3", default-features = false, features = ["rustls"], optional = true }
# **A sold estate's resend sign-in link is mailed by its main** (A16, Gunnar's S10, `metered`
# only): through the ONE shared sender, monetize's `monetize-mail` (P4c, A28 M11: TLS required,
# proper MIME, rustls, no OpenSSL), with its `egress` feature for a box behind its front. Off in
# the default (airgap) build: `xtask/tests/the_airgap_build_mails_nothing.rs`.
monetize-mail = { version = "0.3.0", optional = true, features = ["egress"] }
serde_json = "1"
# Móðguðr (modgunn) — the shared nordisk security gatekeeper. DEFAULT features
# only (the lean `scan` Engine A: CVE/license/provenance verdict), NEVER the
# heavy `scip`/`warehouse` engines. Holger's policy gate + the Tools#3 Security
# pane + the `holger-server scan` verb consume `modgunn::core::Verdict`. Leaf
# crate (serde + serde_json only); breaks the holger↔nornir cycle by design.
modgunn = { version = "0.0.8", default-features = false, features = ["scan"], optional = true }
# Reads the licence a wheel's METADATA or a nupkg's .nuspec declares, so the
# promotion licence policy judges the package and not `None` (B2), and the wheel
# SBOM scanner (`scanner/wheel.rs`) reads METADATA. Bounded zip member reader,
# already in the graph through the nuget/composer backends.
zip_entry = { package = "holger-zip-entry", version = "3.1.0", path = "../repository/zip-entry", optional = true }
# S7 offline vulnerability intelligence: the local advisory database the OSV
# gate reads under an `intel:` block. Network-free unless `fetch` (named by
# `online` below, so the airgap tiers never link it).
holger-intel = { version = "3.1.1", path = "../../intel", optional = true }
# The file and archive inspector + the ClamAV/YARA hook (lane S3): the intake
# chain runs it on every ingested artifact. Network-free leaf; `supply-chain` only.
holger-inspect = { version = "4.0.0", path = "../inspect", optional = true }
# The index rows (lane HOL-PERF-2): the scan worker's bounded per-package index fields,
# stored by the intake and rendered by the format backends. A leaf: serde, serde_json, base64.
holger-index-rows = { version = "4.0.0", path = "../index-rows" }
# The provenance verifier (lane G3a): DSSE / in-toto / SLSA, Sigstore bundles, PEP 740,
# npm provenance and cosign attachments, offline. The intake chain's `provenance` slot.
holger-provenance = { version = "3.0.0", path = "../../provenance", optional = true }
# The scan sandbox (lane SANDBOX-SCAN, owner 2026-10-23): the intake's scans run in
# a hardened rootless podman container, never in this process. `supply-chain` only.
holger-sandbox = { version = "4.0.1", path = "../sandbox", optional = true }
# The job runners behind the Heimdall job API (lane HOL-SANDBOX-TRAIT): every bundle that
# comes in is verified in a job (`job_runner`), never parsed in this process; `intake`.
holger-job-runner = { version = "4.0.0", path = "../../jobs/runner", optional = true }
# The scan job maps its artifact, not reads it: an image of a few GB stays out of the
# heap (the scan program, `sandbox::scan_program`, in the worker). `scanners` only.
memmap2 = { version = "0.9", optional = true }
# The scan job's directory (the artifact and the job's files, mounted read-only).
tempfile = { version = "3", optional = true }
# The licence scanner (`scanner/license.rs`, lane S6): SPDX expressions are
# parsed and checked against the SPDX licence list by Embark's `spdx` (identifiers
# only, no licence texts; its one dependency, smallvec, is already in the graph).
# The package readers open the formats the backends already open: `.crate`, npm,
# sdist and helm tarballs (tar + flate2), the gemspec (tar + gzip), and a `.deb`'s
# copyright file (data.tar.gz / .xz / .zst). All four are already in the graph
# through the cargo, npm, gem, helm and deb backends and the zip reader.
spdx = { version = "0.13", optional = true }
tar = { version = "0.4", optional = true }
# The secrets scanner's rules (lane HOLGER-G3): linear-time byte regexes. `supply-chain` only.
regex = { version = "1", optional = true }
flate2 = { version = "1", optional = true }
lzma-rs = { version = "0.3", optional = true }
zstd = { version = "0.14", optional = true }
toml = { version = "1", optional = true }
# The box's import doors (lane W4, `src/import_door.rs`): the same verified,
# anti-rollback, audit-first bundle import the CLI's `bundle import` runs, now
# reached by `AdminService.ImportBundle`, `POST /-/bundle/import` and the
# `bundle.inbox` job too. Network-free; already in the graph through holger-intel.
update-bundle = { package = "holger-update-bundle", version = "3.0.0", path = "../../update-bundle", optional = true }
# The sync protocol (H4): the signed manifest, the ack, the ledger. Behind `sync`.
holger-sync = { version = "3.0.0", path = "../../sync", optional = true }
# The bundle import's trusted clock speaks chrono (already in the graph through
# holger-update-bundle); named by `bundle-import`.
chrono = { version = "0.4", default-features = false, features = ["clock"], optional = true }
# The downstream's rules (H3): what it may dial (its one upstream), what config it refuses;
# and the front's report to main (HOL-FLOW-FRONT, `front`), which every build's flow draws.
# Pure (serde, base64, blake3), so in every build.
holger-downstream-rules = { version = "3.1.1", path = "../downstream" }
wg-appliance-core = { version = "0.1.5", optional = true }
# The online intel refresh (lane W4, `intel-refresh`): the box mints its own
# ed25519 refresh signer (cert + key on the data disk) to sign what it fetched into
# a `holger.intel/v1` bundle that then takes the SAME verify, floor and audit path
# as a carried one. Already in the lock (nordisk-twin, the dev tests).
rcgen = { version = "0.14", optional = true }
# base64: encode the Basic-auth credential for remote-cache upstreams (Nexus /
# Artifactory). Same version already used agent-side (agent/lib, mannequin).
base64 = "0.23"
# sha2 + hex: verify pull-through cache bytes against the upstream's advertised
# sha256 (Nexus asset metadata / Artifactory X-Checksum-Sha256) BEFORE they are
# written through into the primary — a cache-poisoning guard. Still used directly
# for the SHA-512 checksum-verify path (nornir-hash is SHA-256 only).
sha2 = "0.11"
hex = "0.4"
# The pull-through holds a Maven file against its `.sha1` (and serves `.sha1`/`.md5`).
sha1 = { version = "0.11", optional = true }
# Holger's own APK signing key (lane T8): `rsa` MAKES the RSA key once and writes it
# as PKCS#8; ring signs with it. The same `rsa` 0.9 the tree already carries
# (ssh-key); `jobs` only. RUSTSEC-2023-0071 is in deny.toml: there is no decryption here.
rsa = { version = "0.9", optional = true }
rand_core = { version = "0.6", features = ["getrandom"], optional = true }
md-5 = { version = "0.11", optional = true }
# nornir-hash (edda) — the ONE shared content-hash primitive (LAW #5 dedup). The
# retention/GC content-id (`lib::sha256_hex`), the raw/search/replication content
# ids, and the OCI/PEP digests all consume `nornir_hash::sha256_hex` instead of
# re-rolling `Sha256::new(); hex(...)`. `sha2`-only leaf (no egress) → safe for the
# zero-egress mini/embedded airgap tiers.
nornir-hash = { version = "0.1.1" }
# The data set grows WITHOUT a restart (`src/grow/plane.rs`): edda's `appliance-grow`,
# gunnar's phase machine moved AS IS — the machine, the go-ahead, the journal
# reader, the twin verdict and the capacity ladder, in the `estate` build only (H2's
# estate cut; the write hold of `src/grow.rs` is in every build) (libs only:
# serde, tracing, a pure-Rust gzip for /proc/config.gz). The volume layer over
# korp-installer is its `md` feature, pulled only by `grow-runtime` below; the
# monetize go-ahead verifier only by `metered`.
appliance-grow = { version = "0.1.0", optional = true }
# The estate's clock (edda `estate-clock`, `src/clock.rs`): the box follows the
# FRONT's signed time over the utility network. The default feature set of the
# crate is the report alone (no socket, no crypto, no syscall), so every tier links
# `clock::now()`; asking the front and moving the clock is `clock-sync`.
estate-clock = { version = "0.1.1" }
# The admin view's disks (`src/admin_view/disks.rs`): every storage mount, one statvfs(3)
# each, the floor, /sys/block — product-neutral, so edda's (RULE 2), not holger's. The
# `estate` build only (H2's estate cut): elsewhere the pane says it is not measured.
disk-snapshot = { version = "0.1.1", optional = true }
# gatling, the house fan-out engine, CALLED rather than merely linked.
# stinky-01 §1.1 MEASURED the state this line ends: `rotaryengine` was in
# holger's graph (twice), and `grep -rn gatling server/` found doc
# comments only -- not one call. `replication.rs` now fans its read half
# (fetch + sha256 + destination presence probe) through
# `gatling_map_balanced`, weighted by the `size_bytes` `list()` already returns.
# NEVER rayon (ROOT LAW #0); `cargo tree -i rayon` must keep saying "did not
# match any packages".
#
# Registry, not path. The engine itself (`gatling` below) is registry-only too,
# so `Cargo.lock` holds ONE `rotaryengine` (stinky 16/28, 2026-10-22).
znippy-zoomies = { version = "0.1.26" }
# OIDC token-cache key: digest the bearer token so the cache holds no raw
# secret and keys can't collide into an auth bypass (blake3 is the workspace's
# standard hash, already used for znippy content addressing).
blake3 = "1"
# ── The DSSE + X.509 verifier the ATTESTATION plane reuses (LAW #5) ────────────
# `znippy_common::sign` already ships a spec-exact, pure-Rust DSSE v1 PAE, an
# envelope parser/serializer, `dsse_verify` (signature + chain-to-roots) and the
# `CertStore` trust anchor — producer AND verifier, proven against the DSSE spec's
# own test vector. holger's attestation store CONSUMES that; it does not grow a
# seventh bespoke envelope beside KORPSIG1, holger.update-bundle/v1,
# gunnar-sshsig-v2, monetize's inline signature and Skidbladnir's `.sig.json`.
# Each bespoke envelope costs a verifier nobody outside the house can write.
#
# Optional + gated on THIS crate's `sign` feature, mirroring
# `repository-znippy-package/sign`: `znippy-common/sign` exists only on the local
# znippy, so the default dep graph must not require it. Without `sign` the
# attestation store still stores and queries, but ACCEPTING an envelope fails
# closed by name (`holger-attest-9`) rather than storing it unverified.
#
# ★ PATH, not registry, and the reason is MEASURED, not assumed: published
# znippy-common 0.9.14 carries `src/sign.rs` but `grep -c 'dsse_verify\|dsse_pae'`
# over it returns **0** — the DSSE half landed in the local 0.9.15 and has not
# shipped. The sibling checkout is the documented dev layout (CLAUDE.md "Local dev
# setup") and `xtask/Cargo.toml:43` already path-deps this same crate, so this adds
# no new duplicate to the lock. RELEASE GATE: publishing `holger-server-lib` with
# `sign` needs znippy-common ≥ 0.9.15 on crates.io (or the `[patch.crates-io]`
# override reinstated in the root manifest) — the same condition `server/cli`
# already records at its line 60.
znippy-common = { version = "0.10.4", optional = true }
# The PKCS#11 evidence signer (`pkcs11`, lane HOL-NCSC): the module is loaded at runtime
# (libloading); x509-cert names the CMS attribute type of znippy's `ArchiveSigner`.
cryptoki = { version = "0.12", optional = true }
x509-cert = { version = "0.3", optional = true, default-features = false }
# getrandom: the OS RNG for the marketplace sign-in `state`/`nonce` (what an
# attacker must guess to forge an Entra callback). Tiny, pure, already in the tree.
getrandom = "0.4"
# NOTE: the OIDC/JWKS verification (jsonwebtoken) moved OUT of server-lib into the
# `nornir-auth-oidc` sub-crate (task #57) so a bearer-only tier links none of that
# closure. server-lib no longer depends on `jsonwebtoken` directly — it dispatches
# to the pinned providers via `nornir_service::AuthProvider`.
# The mirror change map's parallel merge-join (`src/mirror.rs`) — the ONE shared
# no-barrier worker-pool engine, and the reason no rayon appears here. This is
# the LEAF crate that `znippy-zoomies` itself re-exports (`pub use ::gatling::{…}`),
# so `gatling::gatling_forkjoin` here and `znippy_zoomies::gatling_forkjoin` in
# `agent/lib` are the same code — taking the leaf keeps the OSM index and the
# codecs out of holger-server-lib's graph. Published as `rotaryengine`: the name
# `gatling` is squatted on crates.io, the LIBRARY name is unchanged.
# REGISTRY ONLY (stinky 16/28, 2026-10-22): this line and draupnir each took the
# sibling `znippy-zoomies/gatling` by path while jera, znippy-zoomies and the
# published draupnir took crates.io, so the lock linked 0.1.13 twice. The
# crates.io 0.1.13 source is byte-identical to the sibling; never a
# `[patch.crates-io]` for it.
gatling = { package = "rotaryengine", version = "0.1.13" }
# Optional + feature-gated: pulled ONLY under `--features testmatrix`. A release
# build (feature OFF) never compiles it, so the functional-status emit calls are
# stripped entirely. Local path resolved via the workspace [patch.crates-io].
# ⚠ Stays optional-NORMAL under a dated RULE 2 exemption — see `[features]
# testmatrix` below for the measured reason it cannot be a dev-dependency.
nornir-testmatrix = { version = "0.2.13", optional = true }
# The GATE verdict vocabulary — `Verdict` / `Rejection` / `GateRejection` /
# `Decision`, and the two rules that must not be re-derived per product (every
# gate runs; an `Err` is an undecided REFUSAL, never an allow). Lifted out of
# gunnar-policy into edda so gunnar's pre-receive gates and holger's promotion
# gates share one vocabulary and neither product depends on the other. Zero
# dependencies, no OS, no filesystem, so every tier links it.
nornir-gate = { version = "0.1.0", optional = true }
# Skíðblaðnir — the reusable upstream-registry FEDERATION library ("the ship"):
# the `UpstreamRegistry` trait + the Artifactory/Nexus/Azure/AWS harbour clients.
# OPTIONAL + pulled ONLY by holger's `federation` (`full`) feature, with
# `default-features = false` so the lean airgap core stays out. holger's
# `mini`/`embedded` tiers never link it (nor any registry-client dep) — the
# smallest holger install carries none of the federation mechanism. holger keeps
# the top-level FEATURE (orchestration/policy in `src/upstream`); the ship keeps
# the mechanism. See `.nornir/holger-tiers.md` (Deployment modes).
skidbladnir = { version = "0.2.3", default-features = false, optional = true }
# jera — the shared durable JOB LEDGER (extracted from nornir, reused by nornir +
# korp). holger routes its real tracked background work (promotions, archive/seal
# ops) through `jera::jobs::JobStore` instead of ad-hoc spawning, so every
# long-running operation lands in ONE durable redb ledger (`running → done|failed`
# + timing) a reader can list. `default-features = false` drops jera's bollard
# container + KVM backends — holger needs only the ledger, not the runners — so the
# airgap core stays lean. See `.nornir/promotion-design.md` (Job ledger).
jera = { version = "0.2.1", default-features = false }
# The off-site copy (`offsite`, lane M5): sealed for the operator's X25519 key
# (ChaCha20-Poly1305 segments, ring's AEAD and HMAC for the S3 signature).
x25519-dalek = { version = "2", features = ["static_secrets"] }
ring = "0.17"
# edda-admit — THE shared admission trait (`Admit::plan / admit / record`), its two
# policies (`PassThrough`, `UsageShare`) and the anonymous DAY TOKEN (holger's own
# `visitors.rs`, moved AS IS into edda; `src/visitors.rs` re-exports it). See
# `src/admission.rs`.
edda-admit = { version = "0.1.0" }
# ── MIGRATION: the one-time move of an incumbent registry into this holger ────
# `MigrationService` reuses the agent's engine verbatim rather than growing a
# second one: `traits::migrate` classifies the repositories and
# `holger_agent_lib::migrate` moves the bytes, so the CLI route
# (`holger-agent --from nexus --to holger`) and the server route cannot drift.
# `default-features = false` drops the agent's `online` feature — the server
# builds its OWN source connectors below and links no second reqwest through
# this edge.
#
# All three are OPTIONAL and pinned to `online`. Migrating from Nexus is an
# OUTBOUND network operation by definition, so the zero-egress airgap tiers
# (`mini` / `embedded`, which omit `online`) must not link it — the appliance
# that may not reach the internet does not carry the code that would.
holger-agent-lib = { version = "3.0.0", path = "../../agent/lib", default-features = false, optional = true }
connector-nexus = { package = "holger-nexus-connector", version = "3.0.0", path = "../../agent/connector/nexus", optional = true }
connector-artifactory = { package = "holger-artifactory-connector", version = "3.0.0", path = "../../agent/connector/artifactory", optional = true }
# nornir-service — the SHARED tonic serve-loop scaffold (edda), the ONE canonical
# `Server::builder()…serve(addr)` bootstrap tail that nornir-server / korp-server /
# dwarves-server also consume (Law #1) instead of hand-rolling four copies. holger
# keeps its OWN richer auth model (OIDC/JWT + mTLS-CN + RBAC) — that is a genuinely
# different model, not a copied twin of the bearer interceptor — so only the serve
# loop is shared here, via `serve_with_tls` (the `tls` feature carries the tonic TLS
# stack holger already links; the plaintext consumers stay TLS-free).
# ── LANE POINTER RETIRED — the flip has happened ───────────────────────────────
#
# These six path deps (five here, one in mannequin/marketplace-mock/Cargo.toml)
# spent the passkey lane pointing at an edda WORKTREE
# (~/git/edda-wt-passkey) because the work spanned both repos and
# edda's branch was deliberately unmerged -- cargo cannot redirect a PATH
# dependency from the command line, so the pointer had to live in the files.
#
# edda's `holger-passkey` branch has landed on edda main, so all six are back on
# the canonical sibling `../../../edda/crates/...`:
#
# server/lib/Cargo.toml
# [dependencies] nornir-service
# [dependencies] nornir-auth-policy
# [dependencies] nornir-auth-webauthn (optional, auth-passkey)
# [dev-dependencies] nornir-auth-oidc (test-support)
# [dev-dependencies] nornir-auth-webauthn (test-support, soft authenticator)
# mannequin/marketplace-mock/Cargo.toml
# [dependencies] nornir-auth-oidc (test-support)
#
# The reason nornir-auth-oidc and the marketplace mock were ever on that list is
# worth keeping: they need nothing from the passkey work, but a path dep resolved
# from TWO checkouts at once is a lockfile collision ("packages ... are different,
# but only one can be written unambiguously"), so every edda crate this workspace
# names must come from ONE tree. That rule outlives the pointer.
#
# A machine-specific absolute-ish path must never reach main again: it builds on
# exactly one laptop and nowhere else.
nornir-service = { version = "0.1.14", features = ["tls", "auth-provider", "grpc-web"] }
# ── Shared AUTH POLICY layer — SHARED in edda (suite auth-unify) ────────────────
# The RBAC role model (`Role`, `AuthConfig`, per-repo ACLs, the opt-in read gate),
# the declarative `AuthMethodConfig`, and `validate_request` were promoted OUT of
# this crate's `auth.rs` into the `nornir-auth-policy` edda leaf so korp / dwarves /
# nornir adopt the SAME login + authorization model — holger is now the reference
# consumer, not the owner. holger's `auth.rs` re-exports the leaf's public API
# unchanged. The per-method provider leaves (`nornir-auth-{bearer,oidc,oauth2,mtls}`)
# are pulled transitively by the leaf's matching features — a tier links ONLY the
# providers it pinned via holger's `auth-*` features, which forward 1:1 into the
# leaf. `default-features = false` keeps the leaf's own default empty so a tier's
# feature set (not the leaf's) decides which providers link. mTLS stays a shared
# edda provider (transport-level CN → identity); the jsonwebtoken/JWKS closure
# rides behind `auth-oidc` (+ reqwest under `online`) so the mini/embedded
# bearer-only tier links none of it.
nornir-auth-policy = { version = "0.1.6", default-features = false }
# `nornir-auth-webauthn` (SHARED edda) — WebAuthn / FIDO2 / passkeys, the
# passwordless / phishing-resistant HUMAN-login standard. OPTIONAL + pinned ONLY by
# `full`: like `saml`, the real backend (`webauthn-rs`) pulls the HEAVY native
# openssl C stack and is MPL-2.0-licensed, so it rides behind the crate's own
# off-by-default `webauthn` feature (turned on only via holger's `auth-webauthn`).
# The mini/embedded/default tiers link NONE of it and no openssl drags in
# (ship-small — proven by `build-space/tests/auth_ship_small.rs`).
# NOTE the two features that select DIFFERENT BACKENDS of this one crate:
# `auth-passkey` -> default features = the PURE-RUST p256/ciborium backend.
# `auth-webauthn` -> the crate's `webauthn` feature = openssl-backed webauthn-rs.
# Only the first is pinned by any tier. See the feature comments above.
nornir-auth-webauthn = { version = "0.2.1", optional = true }
# Sessions, invites and step-up (edda, A34 M-5): revocation floors, hashed
# single-use invites, the step-up window. Behind `auth-passkey` with the rest.
nornir-identity-session = { version = "0.1.1", optional = true, features = ["session", "invites", "step-up"] }
# The identity core (edda, IDENTITY-HOST): sign-up, login, invites, sessions, step-up and
# logout, once for every product; holger is its host (`identity_host`) and serves its
# IdentityService. Behind `auth-passkey` with the rest.
nornir-identity = { version = "0.2.4", optional = true, features = ["grpc"] }
# Opaque subject ids (`passkey:<uuid>`), ceremony ids, and 32 bytes of CSPRNG for
# challenges / invite secrets / the session key -- via the UUID generator rather
# than a second RNG stack, since `getrandom` is already in every tier.
uuid = { version = "1", features = ["v4"], optional = true }
# `monetize-embed` — the SIGNATURE half of the paid seam, and nothing else: the
# canonical form monetize signs an `EntitlementFact` over
# (`signing::fact_message`) and the verifier for it (`signing::verify_fact`).
# holger writes no second canonical form, exactly as gunnar writes none.
#
# From the REGISTRY, not a sibling path (gunnar takes it the same way): holger
# must build without `../monetize` checked out, and this crate is the only edge
# between the two products. OPTIONAL and reached only by `metered`, so an open
# build links no byte of it.
monetize-embed = { version = "0.3.0", optional = true }
# `monetize-product` carries the RUST `EntitlementFact` — the shape monetize
# actually signs. The wire shape is a prost message with different field names and a
# numeric state, so the conversion in `metered::to_signed` is load-bearing: verify
# the wrong shape and every signature fails for a reason no message would explain.
monetize-product = { version = "0.3.0", optional = true }
# **The ONE monetize's pull channel** (`src/fleet_box.rs`, `metered` only): the box
# reports to and pulls its orders from the ONE monetize (monetize never dials it).
# The bare monetize on the front and the plugin self-registration through its door are
# GONE (G16): no estate runs a monetize of its own. OPTIONAL, reached only by `metered`.
holger-fleet = { path = "../../estate/holger-fleet", version = "3.1.1", optional = true }
# The role the pull channel's box pulls as (`fleet_box::role_now`): nordisk-pairing
# `elect::pull_role`, the money-free rule Gunnar reads too (it moved there from
# monetize-embed in 0.5.16). OPTIONAL, reached by `metered` and by `pq-tls` (the estate's
# doors: `pqtls::provider()`, X25519MLKEM768 first, Gunnar's), never by the slim shapes.
nordisk-pairing = { version = "0.5.26", default-features = false, optional = true }
# `nordisk-twin` (Skidbladnir, beside nordisk-pairing) — the twin over mutual TLS
# 1.3, shared with gunnar: the primary's own replication issuer, certify, pin,
# the door's accept loop and the twin's dialler (`twin_tls.rs` is holger's glue).
# In the `estate` build only (H2's estate cut, `src/twin_tls/mtls.rs`), and CONFIG-gated
# there: a RON with no `replication_door` opens no door, mints no issuer and dials
# nothing. Without `estate` a declared door refuses the start by name. Pure Rust on
# ring (rcgen + rustls, both already in the lock); no egress, no native TLS.
nordisk-twin = { version = "0.1.2", features = ["tokio"], optional = true }
# `mallopt(M_ARENA_MAX)` (`memory::bound_allocator`, lane HOL-RSS): already in the lock.
libc = "0.2"
[features]
# ── Deployment modes (see `.nornir/holger-tiers.md`) ───────────────────────────
# Pick exactly one; `embedded` is the default. Federation (the Skidbladnir ship + the
# Azure/AWS/Artifactory/Nexus clients + their deps) is compiled in ONLY by `full`.
# `default` also turns on `online` (outbound HTTP) so the standard build is
# unchanged; drop it (`--no-default-features`) for the zero-egress airgap tier.
# Default (dev/test) pins BOTH modern providers so the full auth surface is
# exercised; the build-thing tier variants pin a SMALLER set (embedded/mini =
# `auth-bearer` alone, `full` = `auth-bearer` + `auth-oidc`).
# The DEV/TEST set, not a ship tier -- it pins every auth provider so one build
# can exercise all of them (see `auth::require_dispatchable_alternative_to_static_
# bearer`, whose refusing branch is otherwise unreachable). The fifth belongs here
# for the same reason: the ceremony door's end-to-end test needs it, and the ship
# tiers (`mini` / `embedded` / `full`) are selected explicitly.
# The front page is NOT here (A28 M14, owner 2026-10-22 "the server has no UI"): a default
# build serves no page (`tests/the_default_server_serves_no_page.rs`). The mount's own tests
# run with `--features front` in `cargo xtask suite-check` (`front:mount`).
default = ["downstream", "fetch", "online", "auth-bearer", "auth-oidc", "auth-oauth2", "auth-mtls", "auth-passkey", "fmt-all", "clock-sync"]
# ── `clock-sync` — a box FOLLOWS the front's clock (edda estate-clock) ────────
# Asks the front for signed time (`peer://<utility>:50123`, a nonce this box chose,
# the front's ed25519 SSHSIG back) and — as PID 1 of its own medium — moves
# CLOCK_REALTIME; otherwise the verified offset is owed in-process (`clock::now`).
# In `default` for the dev/test build only; the ship tiers take it through the
# CLI's `paired` feature (an installer-built box is the one with a front).
clock-sync = ["clock-follow", "estate-clock/fetch", "estate-clock/adjust"]
# ── `clock-follow` — MEASURE the signed peer's time, with NO HTTP client ──────
# The same signed-peer protocol and quorum (estate-clock `poll`, feature `peer`:
# ssh-key + the OS RNG, no ureq/rustls), and never a clock moved: the verified
# offset is owed in-process. What a zero-egress box — an airgapped downstream
# following a site-local `estate-clock run` — links through the CLI's
# `secure_update`, so its bundle verifier reads the ONE signed-time format.
clock-follow = ["estate-clock/peer"]
# ── `admit-share` — the PAID download admission (edda `admit`, `UsageShare`) ──
# 100 GB / 30 d `Full` per customer, then `Reduced`; anonymous → `Floor`, 5 GB / day.
# Without it the only policy is `PassThrough` (every request `Full`), and a config
# naming `UsageShare` is refused at boot by name (`admission::AdmissionConfig`).
admit-share = []
# ── `front` — THE PUBLIC WEB PAGE, and whether THIS binary is the one serving it ─
#
# `holger-front` is the whole public door as one pure function (the page bytes,
# `/-/releases`, the picture, every refusal). This feature is the MOUNT: with it
# on, `exposed::http` claims `/`, `/holger.webp`, `/-/front` and `/-/releases`
# and answers them; with it off, `holger-front` is not a dependency at all — not
# linked, not compiled, not in the artifact — and those paths fall through to the
# repository router exactly as they did before the front page existed.
#
# ## Why it is its own feature and not part of a tier
#
# holger is growing gunnar's deploy shape: the web half on one host, the product
# server on another. In that shape the page is mounted by a BFF that talks to
# this server over gRPC, and this server carries no browser surface at all. That
# is not a new tier — every tier can be either side of that line — so it is an
# orthogonal capability, exactly like `appliance` in `server/cli` (declared,
# implied by NO tier, named explicitly by the variant rows that want it).
#
# ## Which tiers imply it: NONE. Which VARIANTS name it: `full` and `server-local`
#
# * `embedded` / `mini` / `mvp-server` — the airgap + appliance tiers. They do
# NOT get it, and that is the whole point of this commit: `mini` is
# `holger-mikro`, the ISO-installed appliance with no shell, and it was
# carrying a public HTML page for a browser that will never reach it.
# * `full` and `server-local` — named in `holger-recipe`'s variant rows
# (`features = ["full", "front"]`), so the two artifacts that actually face a
# public are byte-for-byte what they were.
# * `default` does NOT carry it (A28 M14, 2026-10-25): the server has no UI, and tier 1
# is the server and its RON. The mount's own tests
# (`the_front_door_is_reached_by_head_and_by_a_write`, `the_mount_names_every_path_the_door_owns`)
# run under `--features front` as suite-check's `front:mount` step.
front = ["dep:holger-front"]
# ── `fmt-*` — WHICH REPOSITORY FORMATS THIS BINARY CAN SERVE (stinky 1.4) ─────
#
# Until 2026-09-21 the 17 backends were unconditional path deps: `holger-mvp`,
# the airgap appliance, linked maven, pip, npm, nuget, go, gem, deb, rpm, helm,
# docker, conda, composer and the znippy package reader for an estate that serves
# `raw` and `rust` (`.nornir/HOLGER-PLAN.md` §4 "stinky 1.4"). One feature per
# format, one umbrella. The ORIGINAL rows name `fmt-all`; the MVP rows name the
# two formats the MVP delivery serves (`fmt-raw`, `fmt-rust`).
#
# What a format that is NOT compiled does: `Repository::backend_from_config`
# refuses BY NAME (`holger-mvp-1`, naming the repository, the format and the
# feature to rebuild with) and `Holger::instantiate_backends` turns that into a
# boot refusal before any repository is wired — never a silently disabled repo.
#
# `fmt-raw` gates nothing in the manifest: the raw backend is `src/raw.rs`, in
# this crate. It exists so a row can NAME the format it serves, and so the
# dispatch arm and the `holger-mvp-1` refusal have one feature to test.
fmt-raw = []
# `rust` — both storage shapes: the znippy archive (`RustRepoZnippy`) and the
# writable file-backed dev registry (`RustRepoFile`). One format, one feature.
fmt-rust = ["dep:repository-file-rust", "dep:repository-znippy-rust"]
fmt-maven = ["dep:repository-znippy-maven"]
fmt-python = ["dep:repository-znippy-python"]
fmt-npm = ["dep:repository-znippy-npm"]
fmt-nuget = ["dep:repository-znippy-nuget"]
fmt-go = ["dep:repository-znippy-go"]
fmt-gem = ["dep:repository-znippy-gem"]
fmt-deb = ["dep:repository-znippy-deb"]
fmt-rpm = ["dep:repository-znippy-rpm"]
fmt-helm = ["dep:repository-znippy-helm"]
# `docker` — `ron_repo_type: "docker"` (znippy-backed, read-only) AND `"oci"`
# (the writable file-backed Distribution v2 registry): `ArtifactFormat::Docker`
# names both crates as one format (`traits::FormatHome::also`).
fmt-docker = ["dep:repository-znippy-docker", "dep:repository-file-oci"]
fmt-conda = ["dep:repository-znippy-conda"]
fmt-composer = ["dep:repository-znippy-composer"]
# `znippy` — znippy-as-the-artifact (`ZnippyRepoZnippy`). `sign` forwards into it
# with `?` so naming `sign` does not drag this format in: the CMS-verified znippy
# path needs BOTH (`wire_verified_znippy` refuses by name otherwise), while the
# `znippy-common/sign` seal reader `sign` also carries is what every row wants.
fmt-znippy = ["dep:repository-znippy-package"]
# `znippy-build-thing` — the forge's build-history (attestation) repository.
fmt-build-thing = ["dep:repository-znippy-build-thing"]
# The umbrella the ORIGINAL rows name: every format, exactly the pre-2026-09-21
# closure (`cargo tree` on `full` is byte-identical in crate set — MEASURED).
fmt-all = [
"fmt-raw",
"fmt-rust",
"fmt-maven",
"fmt-python",
"fmt-npm",
"fmt-nuget",
"fmt-go",
"fmt-gem",
"fmt-deb",
"fmt-rpm",
"fmt-helm",
"fmt-docker",
"fmt-conda",
"fmt-composer",
"fmt-znippy",
"fmt-build-thing",
]
# ── `jobs` — THE JOB QUEUE (stinky 01 §1.3) ───────────────────────────────────
#
# With it OFF the server is byte-for-byte what it was: promote, replicate and
# upload run INLINE on the RPC, the caller waits for the whole run, and a job
# that was in flight when the process died stays `running` for ever. With it ON
# `src/jobs.rs` is compiled: the four long operations become durable rows on the
# SHARED `jera` ledger (no second ledger — jera is the house's one), a gatling
# worker pool (`rotaryengine`, already a dependency of this crate; ROOT LAW #0)
# dequeues them, `JobService` reads them on the admin plane, and a restart
# either RESUMES a job or names it `interrupted`.
#
# It adds NO dependency: `jera` and `gatling` are already unconditional here.
# The feature exists so a tier can decide whether this binary carries a
# scheduler at all — an embedded single-shot engine has no use for one.
#
# Which rows name it: the three ORIGINAL server rows (`embedded`, `mini`,
# `full`) and the MVP server row (`mvp-server`), through `server/cli`'s `jobs`
# passthrough. `default` keeps it so this crate's own tests compile.
# H2 #3 (owner 2026-10-25): `jobs` is the job queue ALONE. It no longer implies
# `supply-chain` or `admission`; a row that wants the intake chain or the door
# admission names it. H3 on H2 (2026-10-26): the APK re-signing key (rsa +
# rand_core) moved from `jobs` to its own `pull-apk`, so the downstream's queue
# never links rsa.
jobs = []
# `pull-apk` — Holger re-signs the APK index it serves with its own RSA key, made
# on first use (rsa + rand_core). The CLI's `jobs` carries it for the upstream rows;
# the downstream serves no APK index and never links rsa (H3).
pull-apk = ["jobs", "dep:rsa", "dep:rand_core"]
# ── The three NON-default planes (lean cut, 2026-10-17). The code stays; the
# default `holger-server` binary does not carry it. A tier that wants one names it.
#
# `supply-chain` — the DEV→PROD promotion engine and its gates (`promote/`), the
# attestation plane (`attest.rs`), hosted SBOMs (`sbom.rs`), the modgunn security
# verdicts (`security.rs`), custom properties (`properties.rs`), and their gRPC
# services (Promotion, Sbom, Property) and HTTP doors (`/-/sbom`, `/-/properties`).
supply-chain = ["intake", "scanners"]
# `intake` (Holger v2.0 H3) — the intake chain WITHOUT its scanners: the states, the
# two-person approval, quarantine, the audit trail, the sealed production store, the
# DSSE/in-toto verification of carried evidence (`attest.rs`) and the verified airgap
# import. The downstream build (server-cli `downstream`) is this: it trusts the
# upstream's SIGNED scan results and runs no scanner of its own. Every
# `cfg(feature = "intake")` in this crate was `supply-chain` before 2026-10-25.
intake = ["dep:sha1", "dep:md-5", "dep:holger-provenance", "dep:tempfile", "dep:nornir-gate", "dep:zip_entry", "dep:tar", "dep:flate2", "dep:zstd", "dep:toml", "bundle-import", "dep:holger-sandbox", "dep:holger-job-runner"]
# `scanners` — the scan engines the intake chain runs at SCANNED and INGEST: the
# sandboxed scan worker (holger-sandbox), the inspector (holger-inspect), the modgunn
# CVE/licence verdicts, the SPDX licence scanner, the secrets rules, the offline intel
# database. The upstream build carries them; the downstream never does.
scanners = ["intake", "dep:modgunn", "dep:memmap2", "dep:holger-sandbox", "dep:holger-inspect", "dep:spdx", "dep:regex", "dep:lzma-rs", "import-door"]
# `sandbox-off` — a TEST build: `promotion.intake.rules: (sandbox: off)` runs the scan
# worker's code in this process. No shipped row names it; a release build refuses
# `off` at boot by name (`holger-sandbox-6`). Named by this crate's own dev-dependency
# on itself, so only test targets carry it.
sandbox-off = ["holger-job-runner?/test-runner"]
# **`heimdall`** (lane HOL-HEIMDALL-02): `engine: heimdall`, the scans and the bundle
# verifies as jobs of Heimdall's protected runner (nordisk-heimdall-container, crates.io), with
# youki-plain and then crun where Heimdall cannot start. A build without it refuses the engine
# by name (`holger-sandbox-10`).
heimdall = ["intake", "holger-job-runner/heimdall"]
# **`heimdall-wasm`** (lane HOL-HEIMDALL-04): the verify-bundle program runs Heimdall's released
# split guest (heimdall-run 0.2, wasmtime) when the job names it; the worker is built with it.
heimdall-wasm = ["intake", "holger-job-runner/heimdall-wasm"]
# The box's import doors' shared body (lane W4, `src/import_door.rs`): the intel
# import and the verified bundle import the CLI verbs, the admin RPCs, the HTTP
# routes and the inbox jobs all run. Light (holger-intel + holger-update-bundle,
# both network-free); the CLI's `secure_update` names it. The bundle half lands
# through the intake chain and so needs `supply-chain`, which implies this.
import-door = ["dep:holger-intel", "bundle-import", "intake"]
# `bundle-import` — the verified, anti-rollback, audit-first bundle import alone
# (holger-update-bundle, network-free), without the intel database (H3).
bundle-import = ["dep:update-bundle", "dep:chrono"]
# `gateway` (Holger v2.0 H3, on H2's names 2026-10-26): the on-prem gateway's own
# rules, on top of the small `downstream` core. Its production store is served as
# native indexes for every format it was handed (Maven, cargo, Python, OCI, Go)
# without a pull-through source, its one outbound line is its upstream Holger, it
# takes no upload, and a config that asks for more is refused at boot. Default-off;
# the CLI's `downstream` (`cargo install holger --features downstream`) turns it on.
# `cfg(feature = "gateway")` in this crate was H3's `downstream` before H2's names won.
gateway = ["downstream", "intake", "bundle-import", "jobs"]
# `intel-refresh` — the `intel.refresh` jera job (lane W4): with `intel.offline:
# false` the box fetches OSV, NVD and KEV itself (`holger-intel/fetch`, the ONE
# outbound client of S7) daily and once at boot, and lands the set through the
# import door. Named by the metered MVP box rows only (server-cli `intel-fetch`),
# never by an airgap tier; without it `intel.offline: false` refreshes nothing.
# It lands the set through the import door, which needs the intake chain: it names
# `supply-chain` itself (H2 #3: `jobs` no longer brings it).
intel-refresh = ["import-door", "jobs", "supply-chain", "holger-intel/fetch", "dep:rcgen"]
# `mirror` — the operator's mirror/replication/migration surface: the durable job
# queue (`jobs`), `ReplicationService`, `JobService` and, with `online`,
# `MigrationService` (the Nexus/Artifactory connectors). The change-map engine and
# `mirror_cycle` stay in every build: the twin follows its primary through them.
mirror = ["jobs", "fetch"]
# **`fetch`** (lane HOL-CDS, owner 2026-10-30): the code that fetches from another Holger —
# the `mirror:` timer and its connector (`mirror_cycle`), the gRPC client of another Holger
# (`object::RemoteHolger`), the sync pull and the sync receipt (the ack). Without it a
# downstream is one-way: packages come in only through its inbox. Every server tier names it;
# the default `holger-downstream` does not (`server/cli/tests/one_way_ships_no_client.rs`).
fetch = []
# **`export-push`** (lane HOL-CDS): the upstream's `export: (push_to, every)` push of each new
# signed bundle into the diode's folder, signed by the box's push signer (rcgen, in the lock).
export-push = ["intake", "jobs", "dep:rcgen"]
# **`sandbox-media`** (lane HOL-CDS-5): the sandbox's medium lay and clamd without the scanners
# (`sandbox::{media, clamd}`): a one-way appliance verifies its bundles in jobs and lays crun and
# the worker from its medium at boot.
sandbox-media = ["dep:holger-sandbox"]
# **The sync protocol** (Holger v2.0 H4, `src/sync.rs`): signed sync manifests with
# invalidations, the downstream's quarantine and signed ack, the upstream's ledger,
# board data and alarm. Default-off. USB mode makes no network call; pull mode
# (`online`) dials only the configured upstream's door. It needs the intake chain
# (the quarantine), not the scanners, so the downstream carries it (H3 on H2).
sync = ["intake", "dep:holger-sync"]
# **`downstream-wg`** (lane HOL-WGDS): a downstream whose upstream is reached over WireGuard
# (`sync.upstreams[].invite`): edda's userspace WireGuard, one peer (the upstream's front),
# the front's address alone routed. Default-off: a downstream build without it dials no UDP.
downstream-wg = ["sync", "fetch", "dep:wg-appliance-core"]
# `migration` — `MigrationService` (the one-time move off Nexus / Artifactory) and
# its agent-lib + two source connectors. Outbound by definition, so it needs `online`.
migration = ["mirror", "online", "dep:holger-agent-lib", "dep:connector-nexus", "dep:connector-artifactory"]
# `admission` — the door admission (`admission.rs`, `admission_grpc.rs`), the
# queued proxy pull-through (`pull_through.rs`) and `AdmissionService`. Without it
# every request is admitted.
admission = []
# ── `metered` — the PAID appliance's half, and the only place money exists ─────
# Compiles `holger.metered.v1` (the signed `EntitlementFact` + its service) and
# links `monetize-embed` for the one signature check. NOT in `default` and in no
# open variant: the community build does not refuse a plan politely, it does not
# contain the vocabulary — proven by `tests/open_build_speaks_no_money.rs`, not
# asserted here. `UsageService` is deliberately NOT behind this: counting is not
# money, and a self-hoster wants the numbers too.
metered = ["estate", "dep:monetize-embed", "dep:monetize-product", "dep:holger-fleet", "dep:nordisk-pairing", "appliance-grow/monetize", "dep:monetize-mail"]
# **The runtime grow ENGINE** (`src/grow.rs`): korp-installer's md volume layer
# (umount + guard tmpfs, md stop, PCI rescan, G1–G6 + mount, the journal) via
# `appliance-grow/md`. Without it `GrowService.Status` answers `engine_present:
# false` and `Start` is refused by name — the set then grows only at a restart
# (korp-start's boot pass). The appliance variants carry it; a developer box
# has no md set to grow.
grow-runtime = ["estate", "appliance-grow/md"]
# **`estate`** (Holger v2.0 H2's estate cut, HCUT 2026-10-26): the code of a box in an
# installer-built estate (front + main + twin) — the twin over mutual TLS (Skidbladnir
# `nordisk-twin`: the replication issuer, certify, the door and the dialler,
# `twin_tls`, `cert_health`), the twin's follow and the recharge lane, the grow plane
# (edda `appliance-grow`: `GrowService`, the capacity ladder) and the admin view's
# disks (edda `disk-snapshot`). NOT in `default` and NOT in `downstream`: the upstream
# (`cargo install holger`) and the on-prem gateway link none of the three crates
# (holger-recipe `downstream_shape`). The estate rows turn it on through the CLI's
# `appliance`/`paired`/`grow-runtime`/`metered`; without it a RON that declares a
# twin door, a twin to follow or a `Twin` mirror source is refused at load by name.
# `sync` (lane HOL-WGDS): an estate main is the upstream of the downstreams its console enrols.
estate = ["dep:nordisk-twin", "dep:appliance-grow", "dep:disk-snapshot", "sync", "fetch", "pq-tls"]
# **Post-quantum TLS on every door** (lane WG-PSK, 2026-10-30): the HTTP/OCI and gRPC doors and
# the process default offer X25519MLKEM768 first (Skidbladnir `nordisk_pairing::pqtls`,
# Gunnar's provider). On in every estate build (`estate`, so `metered`); the slim shapes (the
# plain server, the downstream) keep ring's classical groups and stay under their crate
# bounds (`holger-recipe/tests/every_shape_stays_slim.rs`, `downstream_shape.rs`) until the
# provider lives in a crate of its own (WG-PSK-8).
pq-tls = ["dep:nordisk-pairing"]
# `online` — compile the outbound-HTTP paths (the reqwest OIDC `/userinfo` +
# `jwks_uri` fetch, now in the `nornir-auth-oidc` sub-crate). OMITTED by the
# airgap `mini`/`embedded` tier so NO HTTP-egress client is linked; offline
# JWT-against-JWKS auth still works, and an opaque token / unresolvable-JWKS then
# fails closed instead of reaching for the network. Propagated to the oidc
# provider crate ONLY when that provider is also pinned (`?` = "if enabled").
online = [
"dep:reqwest",
"dep:ureq",
"holger-intel?/fetch",
"nornir-auth-policy/online",
]
# `maven-pgp` — the OpenPGP verifier (holger-provenance `pgp`, rpgp): Maven Central's
# `.asc` against the keys a trust bundle pins as `pgp-keys/<name>` or a floor key of
# `alg: openpgp`, and a source's `require(pgp_keys: [...])`. NOT in `default`, and not
# in the CLI's (T9b, 2026-10-25): rpgp is about 96 crates, and the default `cargo
# install holger` is bounded at 345 (holger-recipe `the_default_tree_stays_small`).
# The CLI's `online` and `full` carry it (they build the Maven pull-through that fetches
# Central's `.asc`). It is NOT a weak `holger-provenance?/pgp` on this crate's `online`:
# cargo writes a weak feature's optional crate into every lock that reaches `online`
# (cargo#10801), so holger-estate-front's and estate-tests' locks would gain rpgp's
# 60 crates without building them. Without it every pgp pin is refused at load by
# name, `[provenance-pgp-not-built]`, and an `.asc` is unverified, never a pass.
maven-pgp = ["supply-chain", "holger-provenance/pgp"]
# ── Pluggable auth providers (task #57) ────────────────────────────────────────
# `auth-bearer` — the tiny static-token / API-key provider (the modern M2M path).
# The one provider every tier ships. `auth-oidc` — the OIDC/OAuth2 (JWT/JWKS +
# device-code) provider; the heavy closure, pinned only by `full`/dev.
auth-bearer = ["nornir-auth-policy/auth-bearer"]
auth-oidc = ["nornir-auth-policy/auth-oidc"]
# `auth-oauth2` — the OAuth 2.0/2.1 provider (RFC 7662 introspection + client-
# credentials + auth-code/PKCE). Pinned only by `full`/dev (like oidc); its HTTP
# path rides behind `online`. Enables Entra ID / Okta / Auth0 / Google / AWS IAM
# Identity Center. Never in mini/embedded (ship-small).
auth-oauth2 = ["nornir-auth-policy/auth-oauth2"]
# `auth-mtls` — the transport-level mTLS (client-cert CN → identity) provider,
# now the shared edda `nornir-auth-mtls` (moved out of this crate). Zero
# crypto/HTTP closure (the TLS handshake + CA trust live in the transport), so
# EVERY tier that terminates mTLS pins it — holger pins it in all tiers, keeping
# the pre-move behaviour (mTLS was always available in-tree).
auth-mtls = ["nornir-auth-policy/auth-mtls"]
# `auth-passkey` — WebAuthn / FIDO2 PASSKEYS, and THIS is the one a tier pins.
#
# It is the sibling of `auth-webauthn` above and the difference is the backend.
# `auth-webauthn` selects the leaf's openssl-backed `webauthn-rs` path, which is
# why it has never been pinned by a tier and still is not: measured against this
# `full` tier it costs 41 crates including `openssl` + `openssl-sys` +
# `foreign-types`, a C library that must exist on the build host of an artifact
# whose entire purpose is to be built and shipped into a sealed environment.
# `auth-passkey` selects the leaf's DEFAULT pure-Rust backend -- p256 + ciborium,
# 24 crates, no FFI at all -- which is what THE LAW permits: FFI only when no
# Rust library exists, and one does. (What is NOT the argument: deny.toml does
# allow MPL-2.0, so webauthn-rs' licence is not the disqualifier.)
#
# And the dispatch objection is gone, which is the other half of why this may be
# pinned. The old note said a two-round-trip
# ceremony has nowhere to run in a seam that sees one stateless credential per
# request. True, and beside the point: the ceremony is not the credential. It
# runs on holger's own `/-/auth/passkey/...` door and mints a MAC'd session
# token, and `AuthMethodConfig::Passkey` dispatches that token through the
# ORDINARY bearer path.
#
# Why holger wants it at all is one word: airgap. Every other human-login method
# points at an IdP, and there is no IdP in an airgapped estate unless somebody
# raises, seals, upgrades and keeps one running inside the bubble. A passkey
# needs nothing outside the box.
auth-passkey = ["nornir-auth-policy/auth-passkey", "dep:nornir-auth-webauthn", "nornir-auth-webauthn/ceremony", "dep:nornir-identity-session", "dep:nornir-identity", "dep:uuid"]
# **`downstream`** — the small core every server carries: the tiny `auth-bearer`
# provider (no jsonwebtoken/JWKS closure) and the `auth-mtls` transport provider.
downstream = ["auth-bearer", "auth-mtls"]
# `full` — everything: federation (the ship + all harbour clients) AND the full
# DISPATCHED modern-auth surface (bearer + OIDC/OAuth2 + mTLS). It links the
# jsonwebtoken/JWKS closure. It deliberately does NOT link
# `auth-webauthn`: that provider has no dispatch arm, so pinning it bought the
# `full` binary the native openssl stack + MPL-2.0 webauthn-rs (+14 crates) and
# nothing else.
full = ["federation", "supply-chain", "mirror", "fetch", "export-push", "admission", "auth-bearer", "auth-oidc", "auth-oauth2", "auth-mtls", "auth-passkey"]
# `federation` — turn on the harbour clients by hiring Skidbladnir's ship. Enables
# the Artifactory/Nexus clients plus the Azure + AWS cloud harbours. Opt-in on every
# product (`federation`).
federation = [
"dep:skidbladnir",
"skidbladnir/federation",
"skidbladnir/upstream-azure",
"skidbladnir/upstream-aws",
]
# Passthrough: holger's `testmatrix` turns on the emitter in nornir-testmatrix so
# the gated `functional_status` calls in this crate's tests + wiring self-checks
# append real rows for `nornir test`. OFF by default ⇒ compiled out for release.
#
# ⚠ DATED RULE 2 EXEMPTION, 2026-09-20 (?33). The move to `[dev-dependencies]`
# was ATTEMPTED and REVERTED, and the reason is a compiler error, not a
# preference: `server/lib/src/grpc.rs:193` defines `functional_status` in LIBRARY
# code, not under `#[cfg(test)]`, and every gRPC handler plus the RBAC write gate
# calls it. With the crate dev-only,
# `cargo check -p holger-server-lib --features testmatrix` fails with
# `E0433: cannot find module or crate nornir_testmatrix` at grpc.rs:195. The same
# holds in `server/cli/src/main.rs:1095` and `agent/cli/src/main.rs:194`. All
# three edges therefore STAY optional-normal.
#
# Why that is safe: RULE 1 already covers the leak RULE 2 fears. `testmatrix` is
# in no `default` set and no variant in `holger-recipe/src/variants.rs` names it,
# so if it ever reached a shipped closure RULE 1 would go red on an unnamed
# feature — the same check that caught `sign` on 2026-09-20. The bench crate
# leaked because its feature WAS in `default`; this one is not. Re-examine this
# exemption if `testmatrix` is ever added to a default set or named by a row.
testmatrix = ["dep:nornir-testmatrix", "nornir-testmatrix/testmatrix"]
# Provenance verification for the `znippy` package repo. Forwards to the package
# repository crate's `sign` feature (which enables `znippy-common/sign`), so a
# `ron_trusted_roots`-configured repo verifies its archive's detached-CMS
# provenance before serving. OFF by default: the wiring then fails closed when a
# repo requests verification (see `wire_verified_znippy`). Kept opt-in because
# `znippy-common/sign` only exists on the local (patched) znippy — see the
# repository-znippy-package Cargo.toml note.
sign = ["repository-znippy-package?/sign", "dep:znippy-common", "znippy-common/sign"]
# The evidence signer's key in a hardware token (lane HOL-NCSC, NCSC P3): a PKCS#11 module
# loaded at runtime (`attestation.signer_hsm`). Off by default: the default and the airgap
# trees load no C library.
pkcs11 = ["sign", "dep:cryptoki", "dep:x509-cert"]
# `wasm-plugins` — load package handlers from a DIRECTORY of `.wasm` at startup,
# in addition to the ones linked in. This is the feature behind the payoff: with
# it on, a new handler is a `ron_handler` string in the config plus a module in
# the plugin directory, and holger is not rebuilt.
#
# OFF in every tier, including `full`, because it carries wasmtime + cranelift.
# A tier that wants it opts in explicitly (`--features wasm-plugins`). With it
# off, `ron_handler` still selects among the LINKED handlers, and a configured
# plugin directory is a loud boot error (`ScanError::NoWasmHost`) rather than a
# catalog that quietly holds fewer handlers than the operator wrote down.
# The 17-advisory wasmtime-27 acceptance this note used to carry is GONE:
# `plugin/host` requires wasmtime 48 (Cargo.lock resolves 48.0.2) and the four
# sandbox-escape exceptions were deleted from deny.toml rather than left to rot.
# The security reason to keep this OFF by default is now the ordinary one — a
# JIT in the request path is surface a tier should opt into on purpose — not an
# outstanding advisory.
wasm-plugins = ["holger-plugin-host/wasm-plugins"]
# `meter-guests` — the Meter guest KIND (ABI v2): its op table, its wire types
# and `MeterRuntime`. With it OFF this binary contains no meter vocabulary at
# all — no verdict type, no capability names, no meter ops. That is strictly
# MORE absence than a cargo `metered` feature gives, because the wasm route also
# removes the guest's own logic, which a `metered` build still compiles in.
#
# It does not imply `wasm-plugins`: the LINKED half of a meter guest is useful on
# its own, and a tier that wants the loaded half names both.
# It carries the module VERIFIER (`verify-modules`, lane E4): a build that can load a
# meter can always check its `.p7s`, and `Holger::plugin_policy` requires a trust root.
meter-guests = ["holger-plugin-host/meter-guests", "holger-plugin-host/verify-modules"]
# **`allow-meter`** (Holger v2.0 H2 #2, owner 2026-10-25): the open rows' meter, BUILT IN.
# holger-plugin-allocation (the open guest, which answers Allow unconditionally) is linked
# as a native meter under its own name (`allocation`) with its own two capabilities, so the
# open rows meter without a wasm runtime. `wasm-plugins` (wasmtime) is only the sold rows'.
allow-meter = ["meter-guests", "dep:holger-plugin-allocation"]
# No `[build-dependencies]`: proto codegen moved to `cargo xtask gen-proto`
# (protox + tonic-build, pure Rust, no `protoc`). build.rs no longer compiles
# protos, so this crate builds with neither `protoc` nor a shell-out.
[dev-dependencies]
# HOL-SIGNED-APT-DNF: rpgp verifies every OpenPGP shape repo_sign writes (tests only; the
# same exact pin as holger-provenance, so the lock does not move).
pgp = { version = "=0.21.0", default-features = false }
# inotify, in `tests/bundle_import_runs_in_a_job.rs`: the server never opens a bundle itself.
libc = "0.2"
# Test builds may scan in-process (`promotion.intake.rules: (sandbox: off)`); a
# release build refuses it (`holger-sandbox-6`). Only the test targets carry this.
holger-server-lib = { path = ".", default-features = false, features = ["sandbox-off"] }
# The pull-through's OCI test verifies Chainguard's real material with G3a's verifier.
holger-provenance = { version = "3.0.0", path = "../../provenance" }
# The sync protocol's documents, for the two-Holger test.
holger-sync = { version = "3.0.0", path = "../../sync" }
webpki-root-certs = "1"
# The estate owner's key in `tests/the_holger_estate_end_to_end_against_the_one_box.rs`
# (plugin-door-wire's ssh-key 0.6). `tests/estate_clock.rs`'s time key is
# estate-clock's own type (`estate_clock::peer::ssh_key`, =0.7.0-rc.11, lane G3b).
ssh-key = { version = "0.6", default-features = false, features = ["ed25519", "std"] }
# The OPEN Meter guest, as a dev-dependency only: the storage-meter proof
# (`tests/the_storage_meters_measure_a_real_filesystem.rs`) runs the REAL
# `AllocationMeter` over the REAL `HolgerMeterServices` so that "the guest's
# capacity() and selftest() stopped saying there was nothing to check" is a
# measurement and not a claim. It does NOT ship in any binary: the guest reaches
# a deployment as a `.wasm` an operator installs (?18), never linked in.
holger-plugin-allocation = { path = "../../plugin/guest-allocation" }
# (holger-monetize-guest and monetize-product-guest left the dev-dependencies in monetize v2.0
# P2 T13: the guest no longer pushes facts or drives the grow lane, so the two tests that drove
# it against this server went with them.)
tempfile = "3"
# ── the metered doors' tests (server/lib/tests/metering_doors.rs) ──────────────
# They SIGN a fact the way monetize-server does — a real Ed25519 key over
# `monetize_embed::signing::fact_message` — so a green there means the real
# signer's bytes would verify here too. A hand-rolled fixture signature would
# only prove this crate agrees with itself.
#
# Unconditional dev-deps, and the test file is `#![cfg(feature = "metered")]`:
# a dev-dep cannot be feature-gated per test target, and these never enter a
# shipped artifact (the release gate does not see dev-deps).
ed25519-dalek = "3" # 3.0 has no `std` feature (2026-10-02, W3)
monetize-embed = "0.3.0"
monetize-product = "0.3.0"
monetize-estate-side = { version = "0.3.0", default-features = false, features = ["pump"] }
# The tests against the REAL ONE box in process (monetize-server, monetize-front,
# monetize-pay, monetize-shop-pairing, holger-estate-front) moved to the detached
# `estate-tests/` on 2026-10-22: those crates exist only in the sibling checkouts,
# and cargo resolves every member's dev-dependencies, so a lone clone could not load.
# (holger-live's bench edge left 2026-10-26, HCUT/B2: no server-lib test named it, and
# every estate edge a root member names is a crate a lone clone must resolve.)
plugin-door-wire = { version = "0.1.1" }
axum ={ version = "0.8", default-features = false, features = ["tokio", "http1", "query", "json"] }
traits = { package = "holger-traits", path = "../../traits", features = ["test-support"] }
# Pack REAL znippy archives in tests — `graceful_backend_init` crafts one that
# records a future on-disk format version and asserts the server disables just
# that repo and keeps serving the rest.
znippy-compress = { version = "0.10.0" }
# The maven end-to-end mirror proof in mirror.rs seals a REAL maven archive with
# the REAL plugin. Both already in Cargo.lock (dev-deps of znippy-maven, and the
# optional `sign` edge above) — direct dev edges, NOT new packages.
znippy-plugin-maven = { version = "0.10.1", features = ["host-decompressors"] }
# `sign`: the meter-trust tests (lane E4) sign a module under a minted CA (`sign::dev`).
znippy-common = { version = "0.10.4", features = ["host-decompressors", "sign"] }
# The OIDC provider's test-support (throwaway RSA keypair + JWT minting) so the
# gRPC promotion tests can mint a signed admin/writer token verified offline
# against an inline test JWKS. Those tests are `#[cfg(feature = "auth-oidc")]`;
# the dev-dep is always present but only compiled into the auth-oidc test build.
nornir-auth-oidc = { version = "0.2.0", features = ["test-support"] }
# The passkey provider's test-support (a SOFT AUTHENTICATOR -- a passkey in
# software) so `tests/passkey_door_e2e.rs` can drive a real register -> login
# round trip over HTTP against a real running holger. Dev-only; it never enters a
# shipped closure, and the `auth_ship_small` law weighs runtime deps only.
nornir-auth-webauthn = { version = "0.2.0", features = ["test-support"] }
# The identity core's conformance suite and TestHost, run against HolgerHost.
nornir-identity = { version = "0.2.4", features = ["grpc", "test-support"] }
# jera (job ledger) — the retention-executor integration test asserts the GC run
# is routed through the shared durable job seam, so the test needs jera directly.
jera = { version = "0.2.1", default-features = false }
# The estate's shipped declaration (`holger_recipe::estate_ron`, lane W2):
# `tests/the_estate_declaration_runs_the_intake_chain.rs` boots the very file
# `cargo xtask estate-media` stages on the box media. A plain, wasm-safe rlib.
holger-recipe = { path = "../../holger-recipe", default-features = false }
rcgen = "0.14"
# `tests/the_twin_follows_over_mutual_tls_across_addresses.rs` presents the
# pairing MEDIUM's own bootstrap certificate to the replication door and needs
# it refused by name — minted by the real installer CA, not a lookalike.
nordisk-pairing = { version = "0.5.26" }
sha2 = "0.11"
hex = "0.4"
# The in-crate zip WRITER, so `tests/real_clients_restore_from_holger.rs` can
# craft the `.nupkg` / Composer dist fixtures it packs into a real archive and
# hands to the REAL dotnet / composer clients. Same fixture the two backends'
# own unit tests use; never on in a shipped build.
zip_entry = { package = "holger-zip-entry", version = "3.1.0", path = "../repository/zip-entry", features = ["fixtures"] }
holger-index-rows = { version = "4.0.0", path = "../index-rows", features = ["fixtures"] }
# Feature-gated test targets: cargo skips them unless the features are on,
# instead of linking an empty binary. The gate's arms (.nornir/testmatrix-arms.json)
# turn them on; the silenced-tests guard reads `required-features`.
# ── The estate's tests (H2's estate cut, HCUT 2026-10-26): the twin over mutual TLS,
# certify, the recharge lane and the pairing renewal run only where `estate` is built
# (the metered run of `cargo xtask test`, and suite-check `estate:twin`).
[[test]]
name = "a_dead_main_is_recharged_from_its_remote_twin"
required-features = ["estate"]
# HOL-PRODUCTS-7: a plain server's signed invite for a direct downstream.
[[test]]
name = "a_plain_server_invites_its_downstream"
required-features = ["sync"]
[[test]]
name = "pairing_renewal"
required-features = ["estate"]
[[test]]
name = "the_twin_follows_over_mutual_tls_across_addresses"
required-features = ["estate"]
[[test]]
name = "the_twin_follows_at_once_when_certified"
required-features = ["estate"]
[[test]]
name = "a_lapsed_repository_is_purged_through_the_admin_door"
required-features = ["metered"]
[[test]]
name = "a_paid_signup_makes_its_first_repository"
required-features = ["metered"]
[[test]]
name = "a_purge_follows_the_one_boxs_grace"
required-features = ["metered"]
[[test]]
name = "a_purge_on_the_main_is_applied_on_its_twin"
required-features = ["metered"]
[[test]]
name = "a_sold_repository_serves_at_once"
required-features = ["metered"]
[[test]]
name = "sold_state_survives_the_twin_and_a_recharge"
required-features = ["metered"]
[[test]]
name = "a_buyer_signs_in_with_the_shops_link_and_owns_their_repository"
# `front`: it reads the public roster (`/-/releases`), the page's door (A28 M14).
required-features = ["metered", "front"]
[[test]]
name = "a_buyer_on_a_shared_holger_redeems_their_signup_and_grants_others"
required-features = ["metered"]
[[test]]
name = "a_visitor_reads_holgers_two_signup_choices"
required-features = ["metered"]
[[test]]
name = "ai_models_on_the_intake_chain"
required-features = ["supply-chain"]
[[test]]
name = "an_estate_admin_invites_its_importer_and_approver"
required-features = ["supply-chain", "auth-passkey"]
[[test]]
name = "an_online_estate_refreshes_its_intel"
required-features = ["intel-refresh", "jobs"]
[[test]]
name = "build_history_repo_wiring"
required-features = ["fmt-build-thing"]
[[test]]
name = "door_reserved_property_floor"
required-features = ["supply-chain"]
[[test]]
name = "estate_clock"
required-features = ["clock-sync"]
[[test]]
name = "every_backend_enumerates_on_the_wire"
required-features = ["fmt-all"]
[[test]]
name = "intake_chain_holds_a_wheel_until_available"
required-features = ["supply-chain"]
[[test]]
name = "intake_security_dashboard"
required-features = ["supply-chain"]
[[test]]
name = "intake_separation_of_duties"
required-features = ["supply-chain"]
[[test]]
name = "a_grpc_upload_into_incoming_is_held_until_available"
required-features = ["supply-chain"]
# An UPLOAD proven against the trust bundle the config names, through the HTTP and gRPC
# upload doors (lane OX-WIRED4).
[[test]]
name = "an_upload_is_proven_against_the_configs_trust_bundle"
required-features = ["supply-chain"]
# The promotion gate's refusal over gRPC PromoteArtifact (lane OX-WIRED2): PromotionService
# is the supply-chain plane.
[[test]]
name = "a_promotion_gate_refuses_over_grpc"
required-features = ["supply-chain"]
# MONETIZE-FIX-2: an operator order's bundle goes through the chain's bundles door as the
# verified order's own in-process principal, auth ENABLED (the metered box's pull channel).
[[test]]
name = "an_ordered_bundle_goes_through_the_bundles_door"
required-features = ["metered", "supply-chain", "fmt-raw"]
[[test]]
name = "tier1_sources_steps_rules"
required-features = ["supply-chain", "online"]
[[test]]
name = "intake_serves_pip"
required-features = ["supply-chain"]
# HOL-MVP-FORMATS: a build of the twelve MVP formats refuses the other four by name (holger-mvp-1).
[[test]]
name = "the_mvp_build_refuses_the_formats_outside_the_mvp_set"
required-features = ["fmt-raw", "fmt-rust", "fmt-npm", "fmt-maven", "fmt-python", "fmt-docker", "fmt-go", "fmt-helm", "fmt-deb", "fmt-rpm", "fmt-znippy", "fmt-build-thing"]
# Chain views (lane HOL-MVP-FORMATS): archive-less deb/rpm/helm/maven3/go repositories are
# views of the intake chain AVAILABLE set.
[[test]]
name = "a_write_outside_the_chain_is_refused"
required-features = ["supply-chain", "fmt-rust"]
# Lane HOL-BUNDLES-DOOR (Holger 3.1.1): a PUT into the bundles door is a verified Holger bundle or refused.
[[test]]
name = "a_bundles_door_takes_only_verified_bundles"
required-features = ["supply-chain", "fmt-raw"]
# SEC-REVIEW 2026-11-01 (P2-P4, P9, the guard): every write door, HTTP, gRPC and the job
# kinds, goes through the intake chain where the policy requires a scan.
[[test]]
name = "every_write_door_goes_through_intake"
required-features = ["supply-chain", "fmt-docker", "fmt-npm", "fmt-deb"]
# HOL-CARGO-PUBLISH-DOOR: cargo publish is a door of the intake chain (real cargo publish,
# install and yank; a malware file and a known-CVE dependency are refused by name).
[[test]]
name = "cargo_publish_walks_the_intake_chain"
required-features = ["supply-chain", "fmt-rust"]
[[test]]
name = "an_upload_is_scanned_as_its_format"
required-features = ["supply-chain", "fmt-maven", "fmt-deb", "fmt-rpm"]
[[test]]
name = "the_chain_serves_apt_dnf_helm_maven_go_views"
required-features = ["supply-chain", "fmt-deb", "fmt-rpm", "fmt-helm", "fmt-maven", "fmt-go"]
# HOL-PERF-2 step 7: the server parses no package to serve an index (its own binary: it
# counts the process's package parses).
[[test]]
name = "the_server_parses_no_package_to_serve_an_index"
required-features = ["supply-chain", "fmt-deb", "fmt-rpm", "fmt-helm", "fmt-maven", "fmt-go"]
[[test]]
name = "pull_through_runs_the_intake_chain"
required-features = ["jobs", "online", "supply-chain"]
# HOL-DEPSET-APPROVE: a pull's whole dependency closure is approved as one set.
[[test]]
name = "a_pull_is_approved_as_one_dependency_set"
required-features = ["jobs", "online", "supply-chain"]
[[test]]
name = "the_npm_cargo_and_python_closures_are_one_set"
required-features = ["jobs", "online", "supply-chain"]
[[test]]
name = "real_clients_over_https_with_a_private_ca"
required-features = ["jobs", "online", "supply-chain", "fmt-deb"]
[[test]]
name = "pull_through_npm_maven_crates"
required-features = ["jobs", "online", "supply-chain"]
[[test]]
name = "pull_through_apk"
required-features = ["jobs", "online", "supply-chain", "pull-apk"]
# The console's three downstream doors over HTTP (enrol, invite, revoke); the metered
# `cargo xtask test` pass carries `sync` (metered -> estate -> sync).
[[test]]
name = "the_downstream_doors_enrol_invite_and_revoke"
required-features = ["sync"]
# Lane OX-WGDS3: a WireGuard downstream answers its WireGuard key and takes its invite
# through its own door (`POST /-/sync/upstreams/invite`), kept once.
[[test]]
name = "a_downstream_takes_its_invite_through_its_door"
required-features = ["downstream-wg"]
# H4: the sync protocol between two Holgers in one process (USB mode, pull mode, the alarm).
[[test]]
name = "sync_between_two_holgers"
# `supply-chain`: its intake blocks name scanner `rules:` (sync itself needs intake only).
required-features = ["sync", "online", "supply-chain"]
# SANDBOX-SCAN: every scan through the scan worker; in-process here, in the real
# container with HOLGER_TEST_SANDBOX=required (`cargo xtask scan-worker tests`).
[[test]]
name = "scan_sandbox_live"
required-features = ["supply-chain"]
[[test]]
name = "replication_runs_as_a_resumable_job"
required-features = ["mirror"]
[[test]]
name = "ai_models_through_the_booted_doors"
required-features = ["supply-chain"]
[[test]]
name = "integration_oci_tls"
required-features = ["fmt-docker"]
[[test]]
name = "metering_doors"
required-features = ["metered"]
[[test]]
name = "migration_door_e2e"
required-features = ["migration"]
[[test]]
name = "mirror_change_token"
required-features = ["fmt-znippy"]
[[test]]
name = "the_open_meter_is_built_in"
required-features = ["allow-meter"]
[[test]]
name = "open_appliance_loads_no_paid_module"
required-features = ["meter-guests"]
[[test]]
name = "passkey_door_e2e"
required-features = ["auth-passkey"]
[[test]]
name = "repository_tokens_for_package_clients"
required-features = ["auth-passkey"]
[[test]]
name = "repository_tokens_with_real_clients"
required-features = ["supply-chain", "auth-passkey"]
[[test]]
name = "real_clients_restore_from_holger"
required-features = ["fmt-nuget", "fmt-composer"]
[[test]]
name = "repositories_wanted_clears_at_the_next_start"
required-features = ["metered"]
[[test]]
name = "the_artifact_trail_lists_every_step"
required-features = ["supply-chain"]
[[test]]
name = "an_airgap_estate_box_imports_signed_intel"
# The shipped estate declaration names a `replication_door`: an estate build only (HCUT).
required-features = ["supply-chain", "estate"]
[[test]]
# HOL-NCSC: the monotone time floor (`time_floor`), over the update-bundle time seam.
name = "the_time_floor_never_moves_back"
required-features = ["bundle-import"]
[[test]]
name = "the_box_imports_bundles_without_a_shell"
required-features = ["supply-chain"]
# Lane HOL-SANDBOX-TRAIT: every bundle is verified in a job, the server opens none.
[[test]]
name = "bundle_import_runs_in_a_job"
required-features = ["supply-chain", "fmt-raw"]
[[test]]
name = "the_estate_declaration_runs_the_intake_chain"
# The shipped estate declaration names a `replication_door`: an estate build only (HCUT).
required-features = ["supply-chain", "auth-passkey", "estate"]
# HOL-MVP-FORMATS: the shipped estate declaration installs the provenance scanner.
[[test]]
name = "the_estate_declaration_runs_the_provenance_scanner"
required-features = ["supply-chain", "auth-passkey", "estate"]
[[test]]
name = "the_estate_declaration_serves_npm_through_the_intake_chain"
# The shipped estate declaration names a `replication_door`: an estate build only (HCUT).
required-features = ["supply-chain", "estate"]
[[test]]
name = "the_fed_public_repositories_are_read_with_no_sign_in"
# The shipped estate declaration names a `replication_door`: an estate build only (HCUT).
required-features = ["supply-chain", "estate"]
[[test]]
name = "the_evidence_rpc_returns_signed_records"
required-features = ["supply-chain", "sign"]
[[test]]
name = "the_license_scanner_signs_its_evidence"
required-features = ["supply-chain", "sign"]
[[test]]
name = "the_meter_is_what_answers"
required-features = ["meter-guests", "supply-chain"]
[[test]]
name = "the_meter_module_is_verified_before_it_loads"
required-features = ["meter-guests", "wasm-plugins"]
[[test]]
name = "the_one_monetizes_orders_reach_holgers_verbs"
required-features = ["metered"]
[[test]]
name = "the_paid_limit_refuses_the_upload_that_would_pass_it"
required-features = ["metered"]
[[test]]
name = "the_provenance_gate_stands"
required-features = ["supply-chain"]
[[test]]
name = "the_rust_path_end_to_end"
required-features = ["online", "fmt-rust"]
[[test]]
name = "the_scanner_framework_records_signed_evidence"
required-features = ["supply-chain", "sign"]
[[test]]
name = "the_shipped_declaration_lets_the_front_certify_the_twin"
required-features = ["supply-chain", "estate"]
# Lane F9: the shipped declaration keeps the front's installer material (its sealed slot).
[[test]]
name = "the_shipped_declaration_keeps_the_fronts_installer_material"
# The shipped estate declaration names a `replication_door`: an estate build only (HCUT).
required-features = ["supply-chain", "estate"]
# A7 row 8 (lane G1): a monetize key fed after the start is enforced without a restart.
[[test]]
name = "a_late_monetize_key_is_enforced_without_a_restart"
required-features = ["metered"]
# A19 row 13 (the lean estate, the SMTP gap): a box behind its front mails its sign-in-again
# link through the front's egress door.
[[test]]
name = "a_lean_box_mails_its_sign_in_link_through_its_front"
required-features = ["metered"]
# MAIL-RELAY (2026-10-28): a laid main fed its keys again by the ONE box mails the first link
# and an admin invite through the relay at once (a fake STARTTLS relay).
[[test]]
name = "a_main_fed_its_keys_again_mails_through_its_relay"
required-features = ["metered", "auth-passkey"]
# A13 / A18 §4 #10: the estate board's consumer count is the door admission's book, so the
# test that counts consumers DECLARES `admission` — it is no longer reached by feature
# unification alone. `cargo xtask suite-check` runs it (`board:admission`).
[[test]]
name = "the_estate_board_counts_upstreams_downstreams_and_consumers"
required-features = ["admission"]
# Holger v2.0 H3: the downstream refuses a config that asks for more than one upstream
# Holger and pins its egress to that upstream (`holger-downstream-1/2`).
[[test]]
name = "a_downstream_dials_only_its_upstream"
required-features = ["gateway"]
# Lane HOL-EGRESS-WG: a WireGuard downstream that took its signed invite dials the invite's
# endpoint (pinned beside its upstream's door), and still no other peer.
[[test]]
name = "a_downstream_dials_its_invites_wireguard_endpoint"
required-features = ["gateway", "downstream-wg"]
# Lane OX-WGDS3: a downstream medium's declaration (holger-recipe `downstream_ron`) loads.
[[test]]
name = "a_downstreams_declaration_loads"
required-features = ["sync"]
[[test]]
name = "a_chain_download_streams_from_the_sealed_store"
required-features = ["supply-chain"]
[[test]]
name = "a_znippy_download_streams_from_the_archive"
required-features = ["supply-chain", "fmt-python"]
[[test]]
name = "a_streamed_download_is_the_whole_body_answer"
required-features = ["fmt-python", "fmt-npm", "fmt-maven", "fmt-deb", "fmt-rpm"]
[[test]]
name = "every_znippy_download_streams"
required-features = ["fmt-helm", "fmt-gem", "fmt-go", "fmt-conda", "fmt-rust", "fmt-composer", "fmt-docker", "fmt-nuget", "fmt-python"]
[[test]]
name = "catalogs_and_hot_crates_hold_no_package"
required-features = ["fmt-composer", "fmt-nuget", "fmt-rust"]