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

nordisk/holger

docs/ai-context.md

holger — AI context (compact)

Read this file first. It is the primary context for reasoning about this repository. Use the companion ai-context-full.md only for exact public-API signatures and file:line source lookup — do not load it for routine work.

Generated summaries never override active code. Before changing behavior, inspect the referenced implementation and its tests.

Snapshot

Field Value
Repository holger
Source revision a843b3260e016fc17d113ab42dba20344c5e832e
Working tree Dirty
Reproducible snapshot Non-reproducible
Generation mode development
Generator version kvasir 0.2.1
Maintained metadata .nornir/ai-context.toml
Generated at 2026-08-13T21:41:29+02:00 (source: commit)

⚠ Dirty working tree — this snapshot is NOT reproducible. It reflects uncommitted local changes, not commit a843b3260e01.

Category Count Included items
Cargo workspace packages 42 see the workspace dependency graph
Detached Rust packages 0 —
Packages with indexed API surface 42 42 workspace + 0 detached
Binary targets 19 [[bin]] + src/main.rs + src/bin/*
Library targets 39 packages with src/lib.rs

The indexed count (42) is the 42 workspace members that expose a src/ tree plus 0 detached package(s) (own [workspace], indexed for their API but outside the dependency graph). Detached packages are in the API index; they are not in the workspace dependency graph.

AI source-of-truth hierarchy

When sources disagree, trust the higher item. Strongest first:

  1. Executable tests (they assert real behavior)
  2. Active runtime wiring (what the daemon actually constructs and registers)
  3. Implementation (the code that runs)
  4. Configuration and feature definitions (what is switched on)
  5. Schemas and persisted data contracts
  6. Rustdoc (author intent — weaker than the code it describes)
  7. Current architecture documents
  8. Generated summaries (this pack included)
  9. Historical documents (design notes, superseded proposals)
  10. Inferred intent (weakest — a guess)

State explicitly:

System summary

Field Value
Purpose Secure, airgap-first artifact registry (a Nexus/Artifactory replacement) whose system of record is immutable, content-addressed znippy archives. One server serves ~15 package ecosystems (Cargo, Maven, PyPI, npm, NuGet, Go, RPM, Deb, Gem, Conda, Helm, Docker/OCI, Composer, raw) over gRPC + a native-protocol HTTP door; a separate internet-side agent moves packages across the airgap as znippy archives.
System role Artifact registry + airgap transfer agent + egui operator UI, feature-gated into embedded/mini/full server tiers and thin/fat UI tiers from one source tree.
Language Rust (feature-gated workspace: embedded/mini/full server tiers, thin/fat UI tiers)
Major binaries holger-server, holger-agent, holger-server-ui, holger-server-ui-embedded, holger-server-ui-mini, holger-server-ui-mikro, holger-mannequin, xtask
Primary runtime components Server (holger-server-lib): RON graph -> wire_holger -> 8 tonic gRPC services + a native-protocol HTTP door.; Repository backends (RepositoryBackendTrait) per ecosystem: znippy archives read-only; file-rust/file-oci/raw writable.; Agent (holger-agent-lib): Source x Target ConnectorTrait matrix with bounded upload pool and partial-success tallies.; Promotion: DEV->PROD content-addressed, blake3-verified seal (seal-once; withdrawal appends to lineage).; Airgap (holger-airgap): pure-Rust plan that shells out once to the populate driver.; UI (holger-server-ui, facett/egui): thin RemoteHolger gRPC client or in-process LocalHolger, sliced into gui/mini/mikro tiers.

Non-goals (explicit):

AI reasoning warnings

Short operational cautions for interpretation (one line each):

Runtime wiring map

A crate or function existing is not proof of wiring. Status uses the runtime vocabulary.

→ Runtime entry points & constructors: ai-context-full.md.

Capability Interface Implementation Runtime registration Configuration Tests Status
Config load + graph wiring RON Holger root server/lib/src/lib.rs:355 server/lib/src/lib.rs:113 --config <file.ron>; repositories/exposed_endpoints/storage_endpoints/auth/audit server/lib/tests/graceful_backend_init.rs Runtime wired
Repository backend registration RepositoryBackendTrait server/lib/src/repository.rs:207 server/lib/src/repository.rs:186 ron_repo_type match: rust|rust-remote|maven3|pip|npm|nuget|go|gem|deb|rpm|helm|docker|oci|raw|conda|composer|group|znippy|{nexus,artifactory,azure,aws}-remote — status note: one failing repo is disabled, hard-fail only if ALL fail server/lib/tests/graceful_backend_init.rs Runtime wired
gRPC services (8) tonic services server/lib/src/grpc/services.rs server/lib/src/lib.rs:1084 ExposedEndpoint.ron_url (bare SocketAddr); per-service body caps = ron_max_body_bytes — status note: Repository, Archive, Admin, Promotion, Search, Sbom, Property, Replication server/lib/tests/integration_oci_tls.rs Runtime wired
HTTP door (native package protocols + OCI) package-client HTTP/2 server/lib/src/exposed/http.rs server/lib/src/lib.rs:889 ExposedEndpoint.ron_http_url; /healthz /readyz /metrics open; /-/search /-/sbom /-/properties — status note: CLAUDE.md wrongly says 'No HTTP/REST API surface' server/lib/tests/integration_oci_tls.rs Runtime wired
Authentication nornir_service::AuthProvider (edda) server/lib/src/auth.rs server/lib/src/auth.rs:445 auth.methods: Oidc|OAuth2|Mtls|StaticToken; provider must be feature-pinned by the tier — status note: bearer+mTLS all tiers, OIDC/OAuth2 full+default only. WebAuthn dep present but NOT dispatched (deliberate). server/lib/src/auth.rs Runtime wired
Authorization (RBAC) Role floor (Denied<Reader<Writer<Admin) server/lib/src/auth.rs server/lib/src/grpc.rs:244 auth.roles/default_role/repo_roles; require_auth_for_reads (default false) — status note: writes fail-closed when RBAC configured; reads open unless require_auth_for_reads; admin for promote/delete/replicate server/lib/src/grpc.rs Runtime wired
Audit logging audit::AuditLog trait server/lib/src/audit/arrow_ipc.rs server/lib/src/grpc.rs:218 audit.dir (default holger-audit under $HOLGER_DATA); enabled (default true) — status note: append-only Arrow IPC, 0700/0600. Open failure ⇒ NoopAuditLog fallback (fail-open, loud). server/lib/src/audit/arrow_ipc.rs Runtime wired
Promotion (DEV→PROD seal) PromotionEngine server/lib/src/promote/mod.rs server/lib/src/lib.rs:971 HOLGER_PROMOTE_ENFORCE/_ADVISORY_DB/_DENY_LICENSES/_REQUIRE_PROPERTY; prod store <data_root>/holger-prod-store — status note: CLAUDE.md wrongly says 'not implemented' server/lib/src/promote/mod.rs Runtime wired
Retention / GC retention policy scan server/lib/src/lib.rs:637 server/cli/src/main.rs holger-server retention --keep-last-n/--max-age-secs/--max-total-bytes/--execute (dry-run default) — status note: deletes audited as ident holger-retention-gc server/lib/tests/retention_executor.rs Runtime wired
Replication replication::replicate_pairs server/lib/src/replication.rs:168 server/lib/src/grpc/services.rs:1296 holger-server replicate --from --to [--execute] (dry-run default); ReplicationService.Replicate (admin-gated) — status note: one-shot, sha256-idempotent skip, admin fail-closed. Continuous/multi-site NOT built. server/lib/src/replication.rs Runtime wired
Airgap populate PopulateArgs plan airgap/src/lib.rs server/cli/src/main.rs holger-server airgap --stack/--scale/--target/--auth (mtls reuses connect cert); shells distribution/airgap/populate — status note: pure-Rust plan; single shell-out boundary; --dry-run returns before driver discovery airgap/src/lib.rs Runtime wired
UI local vs remote HolgerObject server/lib/src/object.rs server/ui/src/data.rs holger-server-ui ENDPOINT/--token/--ca/--cert/--key; --demo builds in-process LocalHolger — status note: RemoteHolger (tonic + bearer interceptor) or LocalHolger (direct backend calls server/ui/tests/remote_grpc.rs Runtime wired
Build tiers cargo features build-space/build-variants.toml server/cli/src/main.rs server: --no-default-features --features embedded|mini|full; ui: gui|gui-embedded|gui-mini|gui-mikro — status note: mutually-exclusive server tiers (compile_error! guard); link-audited by auth_ship_small.rs build-space/tests/build_variants_no_drift.rs Runtime wired

Capability matrix

Support and limitations only (the security rationale lives in the invariants).

Plane Capability Read Write/Act Human approval Runtime wired Persistence Main limitation Evidence
Ecosystem Cargo/Rust Yes Partial No Yes native index: Synthesized; znippy backend: Yes; writable: Yes; proxy upstream: Feature-gated; generic-fetch: Yes; browse: Yes cargo publish only on writable dev-mode znippy archive (static archive → 403); file backend has no publish (config.json api:null); rust-remote proxy needs online server/repository/znippy-rust/src/lib.rs; server/repository/file-rust/src/lib.rs
Ecosystem Maven Yes No No Yes native index: Passthrough+Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes no timestamped unique-build snapshots; empty typed view falls back to .jar filename parse (non-jar files invisible to fallback) server/repository/znippy-maven/src/lib.rs
Ecosystem Python/PyPI Yes No No Yes native index: Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes PEP 503 HTML + PEP 691 at explicit /simple/{name}/json (no Accept negotiation); layout fixed at packages/{norm}/{file} server/repository/znippy-python/src/lib.rs
Ecosystem npm Partial No No Yes native index: Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Partial; browse: Partial packument is VIEW-ONLY: a generic-reader (flat-file ingested) archive 404s the packument, breaking npm install; tarball exact-path still serves; no archive_files/archive_info server/repository/znippy-npm/src/lib.rs
Ecosystem NuGet Yes No No Yes native index: Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: No only PackageBaseAddress (flat container) — no Registrations/Search ⇒ exact-version pulls only; list() empty; no archive view server/repository/znippy-nuget/src/lib.rs
Ecosystem Go Yes No No Yes native index: Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: No @v/list synthesized from .zip scan; versions with only .info/.mod are invisible in list; list() empty; no archive view server/repository/znippy-go/src/lib.rs
Ecosystem RPM Yes No No Yes native index: Passthrough+Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes repomd.xml+primary.xml(.gz) synthesized (no filelists/other.xml); a chain view and an archive-backed repository sign repodata/repomd.xml.asc under the box's repository key (dnf repo_gpgcheck=1, gpgkey=/-/repo-key.asc); meta from typed view → native binary header parse (64 MiB cap) → filename NEVRA epoch=0 server/repository/znippy-rpm/src/lib.rs
Ecosystem Debian/APT Yes No No Yes native index: Passthrough+Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes Packages+Release synthesized (real SHA256); control from typed view → native ar+control.tar parse → filename; Packages.gz written once; a chain view and an archive-backed repository sign InRelease + Release.gpg under the box's repository key (holger repo-key create, public key at /-/repo-key.asc, apt signed-by=), unsigned without one server/repository/znippy-deb/src/lib.rs
Ecosystem RubyGems Yes No No Yes native index: Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes real compact-index (info/{name} checksum = real sha256); empty without the typed view; no legacy specs.4.8 API server/repository/znippy-gem/src/lib.rs
Ecosystem Conda Yes No No Yes native index: Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Partial repodata.json per subdir (real sha256 + blake3); .conda (v2) packages NOT in the synthesized index (packages.conda always {}) server/repository/znippy-conda/src/lib.rs
Ecosystem Helm Yes No No Yes native index: Passthrough+Synthesized; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes classic (non-OCI) repo; index.yaml passthrough wins else generated; root-level tgz URLs only server/repository/znippy-helm/src/lib.rs
Ecosystem Docker/OCI Yes Yes No Yes native index: Passthrough+Synthesized; znippy backend: Partial; writable: Yes; proxy upstream: —; generic-fetch: Yes; browse: Yes file-oci is a full writable Distribution-v2 registry (chunked/monolithic push, digest-verified); the znippy-docker backend is READ-ONLY GET-only (no push/HEAD, list() empty, no referrers) server/repository/file-oci/src/lib.rs; server/repository/znippy-docker/src/lib.rs
Ecosystem Composer Yes No No Yes native index: Passthrough; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: No pure passthrough — archive must already carry Composer v2 metadata; only packages.json emitted inline; no synthesis from bare zips; list() empty server/repository/znippy-composer/src/lib.rs
Ecosystem raw Yes Yes No Yes native index: No; znippy backend: No; writable: Yes; proxy upstream: No; generic-fetch: Yes; browse: Yes path IS the address (version-less coords); PUT/POST/DELETE, sha256-verified delete; the ONLY backend implementing coordinate_for_path (so only raw HTTP serves are quarantine-gated by coordinate) server/lib/src/raw.rs
Ecosystem Znippy-native Yes No No Yes native index: No; znippy backend: Yes; writable: No; proxy upstream: No; generic-fetch: Yes; browse: Yes Holger-native surface (no ecosystem protocol); optional CMS provenance verify behind sign (ron_trusted_roots) — without sign a trusted-roots config fails wiring closed server/repository/znippy-package/src/lib.rs

Non-negotiable invariants

The authoritative invariant list. Strength uses the invariant vocabulary.

→ Enforcement APIs: ai-context-full.md.

Invariant Contract Strength Enforced at Tests
connector-same-origin nexus/artifactory credentials only ever attach to a request whose scheme+host+port equals the configured base; cross-origin downloadUrls are refused before any request is built. Enforced agent/connector/nexus/src/lib.rs:131; agent/connector/artifactory/src/lib.rs:125 agent/connector/nexus/src/lib.rs; agent/connector/artifactory/src/lib.rs
world-ssrf-allowlist The world connector downloads only https URLs whose host is in the CDN allowlist (crates.io, static.crates.io, pypi.org, files.pythonhosted.org). Enforced agent/connector/world/src/lib.rs:76 agent/connector/world/src/lib.rs:220
no-traversal Untrusted paths (upstream asset paths, archive members, raw relpaths, sbom/property coords, promote to_repo) are component-validated or sanitized to a single segment before any filesystem join. Enforced agent/lib/src/operations.rs:108; server/lib/src/raw.rs:60; server/lib/src/promote/mod.rs:214 agent/lib/src/operations.rs; server/lib/src/raw.rs; server/lib/src/promote/mod.rs
promote-seal-once A (to_repo, coord) is sealed into the PROD store exactly once via create_new; the loser of a race gets AlreadyPromoted and writes nothing. Enforced server/lib/src/promote/mod.rs:578 server/lib/src/promote/mod.rs
withdraw-is-append Withdrawal appends a Withdrawn entry to the lineage log; the sealed blob and ref are never touched. Current state is derived by replaying history. Enforced server/lib/src/promote/stages.rs:289 server/lib/src/promote/mod.rs
format-version-pin A znippy archive recording a format version above MAX_SUPPORTED_ZNIPPY_FORMAT (=3) or non-numeric is refused — at open (repo disabled, server serves on) and per-request (503 + reason). Enforced traits/src/znippy_format.rs:61 server/repository/znippy-rust/tests/format_version_gate.rs
read-gate-opt-in-fail-closed require_auth_for_reads defaults false (open reads); when true, no identity ⇒ 401, denied role ⇒ 403, health stays open, and gate-with-no-auth-methods refuses everything (warned at boot). Enforced server/lib/src/auth.rs; server/lib/src/grpc.rs:350 server/lib/src/grpc.rs; server/lib/src/exposed/http.rs
write-gate-fail-closed Writes require authentication (plus RBAC when policy is configured) on BOTH gRPC and HTTP, checked before the body is collected; a read-only repo returns 403 pre-body. Enforced server/lib/src/grpc.rs:244; server/lib/src/exposed/http.rs server/lib/src/grpc.rs
audit-append-only The audit log is one fresh Arrow-IPC segment per process, only appended; replay skips a corrupt segment without losing intact ones. Enforced server/lib/src/audit/arrow_ipc.rs server/lib/src/audit/arrow_ipc.rs
transfer-partial-success Agent transfer/push operations return Ok(()) even when individual assets failed to download or upload; the failure count is data (stdout tallies), not an error. Callers must inspect the tally, not just the Result. Enforced agent/lib/src/operations.rs agent/lib/src/operations.rs
tier-mutual-exclusion The server tiers embedded/mini/full are mutually exclusive; a compile_error! guard enforces it, and link-audits assert forbidden crates (webauthn, openssl) never link into a tier. Enforced server/cli/src/main.rs; build-space/build-variants.toml build-space/tests/auth_ship_small.rs; build-space/tests/build_variants_no_drift.rs
storage-endpoint-is-stub StorageEndpoint::backend_from_config is a documented no-op; the live backend hangs off Repository, not StorageEndpoint. Editing storage/mod.rs changes nothing at runtime. Enforced server/lib/src/storage/mod.rs; server/lib/src/repository.rs —
sec-ssrf-world-connector Download URLs from upstream JSON must be https + host-allowlisted before any request Enforced agent/connector/world/src/lib.rs:76 agent/connector/world/src/lib.rs:220
sec-ssrf-credential-exfil-nexus-artifactory Registry-supplied downloadUrl must be same scheme+host+port as base before creds attach Enforced agent/connector/nexus/src/lib.rs:131; agent/connector/artifactory/src/lib.rs:125 agent/connector/nexus/src/lib.rs; agent/connector/artifactory/src/lib.rs
sec-redirect-disabling All connector HTTP clients use redirect::Policy::none() — only the initial URL is vetted Enforced agent/connector/world/src/lib.rs; agent/connector/nexus/src/lib.rs; agent/connector/artifactory/src/lib.rs —
sec-unbounded-read Response bodies capped (8 GiB artifact / 256 MiB JSON); tar entries capped 8 GiB (MAX_TAR_ENTRY_BYTES) Enforced agent/connector/world/src/lib.rs:38; agent/lib/src/operations.rs:30 —
sec-server-body-cap put payloads capped (default 1 GiB, ron_max_body_bytes; shared gRPC/HTTP knob) Enforced server/lib/src/grpc.rs:41; server/lib/src/exposed/http.rs:642 server/lib/src/grpc.rs
sec-path-traversal-agent Upstream asset + archive member paths reject absolute/Prefix/.. per-component Enforced agent/lib/src/operations.rs:108 agent/lib/src/operations.rs
sec-path-traversal-server Raw relpaths reject empty/./../backslash/NUL; sbom/property coords sanitized; promote to_repo sanitized to one segment Enforced server/lib/src/raw.rs:60; server/lib/src/promote/mod.rs:214 server/lib/src/raw.rs; server/lib/src/promote/mod.rs
sec-atomic-writes temp+rename for raw puts, promote blobs, properties, sbom Enforced server/lib/src/raw.rs:113; server/lib/src/promote/mod.rs:583 server/lib/src/raw.rs
sec-immutable-promote-seal A (to_repo, coord) seals exactly once via create_new; re-promote refused, race-safe Enforced server/lib/src/promote/mod.rs:578 server/lib/src/promote/mod.rs
sec-withdrawal-append Withdraw appends a Withdrawn lineage entry; blob+ref never touched; state is a history replay Enforced server/lib/src/promote/stages.rs:289 server/lib/src/promote/mod.rs
sec-content-hashing-integrity promote store blake3 (address + re-verify on fetch); cargo cksum + raw delete + replication dedup via nornir_hash::sha256_hex Enforced server/lib/src/promote/mod.rs:575; server/lib/src/replication.rs:40 server/lib/src/promote/mod.rs
sec-writable-znippy-locking Writable-archive put is read-modify-write under write_lock serializing contains-check + append Enforced server/repository/znippy-rust/src/lib.rs:59 server/repository/znippy-rust/src/lib.rs
sec-znippy-format-version-pin Archive recording version > MAX_SUPPORTED_ZNIPPY_FORMAT (=3) or non-numeric is refused at open and per-request (503) Enforced traits/src/znippy_format.rs:61 server/repository/znippy-rust/tests/format_version_gate.rs
sec-audit-permissions Audit dir 0700, segments 0600, never rewritten Enforced server/lib/src/audit/arrow_ipc.rs:71 —
sec-error-leak-prevention Backend errors laundered to opaque 'internal error'; detail only in server log; quarantine served as not_found Enforced server/lib/src/grpc.rs:47; server/lib/src/grpc/services.rs:90 server/lib/src/grpc.rs
sec-quarantine-gate A quarantined artifact is refused at every serve door as not_found (existence not leaked) Enforced server/lib/src/grpc/services.rs:90 server/lib/src/grpc.rs

Security-sensitive code map

Maps a concern to its authoritative invariant (above) — enforcement and tests are not restated here.

Concern Invariant Security boundary Consequence if broken
SSRF (world connector) sec-ssrf-world-connector Download URLs from upstream JSON must be https + host-allowlisted before any request Agent fetches attacker URLs (cloud metadata, internal services)
SSRF + credential exfil (nexus/artifactory) sec-ssrf-credential-exfil-nexus-artifactory Registry-supplied downloadUrl must be same scheme+host+port as base before creds attach Basic/Bearer creds leak to arbitrary hosts
Redirect disabling sec-redirect-disabling All connector HTTP clients use redirect::Policy::none() — only the initial URL is vetted A 3xx from a compromised allowlisted host bypasses the origin guard and smuggles creds (no redirect-following test exists — Unverified)
Unbounded read sec-unbounded-read Response bodies capped (8 GiB artifact / 256 MiB JSON); tar entries capped 8 GiB (MAX_TAR_ENTRY_BYTES) Malicious upstream/archive OOMs the agent (no direct cap-trip test — Unverified)
Server body cap sec-server-body-cap put payloads capped (default 1 GiB, ron_max_body_bytes; shared gRPC/HTTP knob) Upload DoS
Path traversal (agent) sec-path-traversal-agent Upstream asset + archive member paths reject absolute/Prefix/.. per-component RCE on the agent host via file drop outside the destination
Path traversal (server) sec-path-traversal-server Raw relpaths reject empty/./../backslash/NUL; sbom/property coords sanitized; promote to_repo sanitized to one segment Read/write outside the store roots
Atomic writes sec-atomic-writes temp+rename for raw puts, promote blobs, properties, sbom Torn files served as artifacts
Immutable promote seal sec-immutable-promote-seal A (to_repo, coord) seals exactly once via create_new; re-promote refused, race-safe PROD overwrite / supply-chain tamper
Withdrawal = append sec-withdrawal-append Withdraw appends a Withdrawn lineage entry; blob+ref never touched; state is a history replay Sealed history rewritten
Content hashing / integrity sec-content-hashing-integrity promote store blake3 (address + re-verify on fetch); cargo cksum + raw delete + replication dedup via nornir_hash::sha256_hex Tampered blob served
Writable znippy locking sec-writable-znippy-locking Writable-archive put is read-modify-write under write_lock serializing contains-check + append Two concurrent puts corrupt the .znippy
znippy format-version pin sec-znippy-format-version-pin Archive recording version > MAX_SUPPORTED_ZNIPPY_FORMAT (=3) or non-numeric is refused at open and per-request (503) A future archive crashes/mis-parses the reader. NOTE: only znippy-rust consumes this gate; other znippy backends rely on znippy-common's internal check.
Audit permissions sec-audit-permissions Audit dir 0700, segments 0600, never rewritten Local users read principal identities / access patterns (no perms-assertion test — Unverified)
Error-leak prevention sec-error-leak-prevention Backend errors laundered to opaque 'internal error'; detail only in server log; quarantine served as not_found Archive/file paths and internals leak to anonymous readers (redaction itself Unverified)
Quarantine gate sec-quarantine-gate A quarantined artifact is refused at every serve door as not_found (existence not leaked) Quarantined/withdrawn artifact still served; existence oracle

Failure semantics

An Ok result may still contain partial failures; degraded operation is not full operation.

→ Error/Result types: ai-context-full.md.

Operation Fatal errors Partial success Retry Cleanup/rollback Degraded mode Observable result Evidence
Config parsing (RON) yes — malformed RON aborts startup via ? no none nothing written yet — stderr + non-zero exit server/lib/src/lib.rs
Graph wiring (wire_holger) yes on structural errors no — the graph is built once, then frozen (raw pointers, never mutated) none n/a — startup error server/lib/src/lib.rs
Backend initialization only if ALL repos fail yes — a failing repo is disabled + logged, the rest serve; the disabled repo is absent from FastRoutes (per-request not_found) none n/a — log::error per repo + warn summary server/lib/tests/graceful_backend_init.rs
gRPC / HTTP bind yes — a bind failure on an endpoint aborts that endpoint's task endpoints are independent tasks none n/a — startup error / task log server/lib/src/lib.rs
Authentication no — per-request only n/a none n/a — gRPC unauthenticated / HTTP 401; refusals audited as the attempted subject server/lib/src/auth.rs
Authorization no — per-request n/a none n/a — gRPC permission_denied / HTTP 403; read-only repo write → 403 pre-body server/lib/src/grpc.rs
Audit sink init no — falls back to NoopAuditLog audit silently off after a loud error (availability > audit) none n/a — log::error 'audit DISABLED' server/lib/src/lib.rs
Audit event write no — never fails the request the event line is lost none n/a — log::warn 'audit record failed' server/lib/src/grpc.rs
Connector repository/asset listing yes — a list error aborts the operation (dump_all_repos: a repo task Err or list_assets Err is fatal) no none n/a — CLI error agent/lib/src/operations.rs
Connector download no (in most transfer ops) yes — a failed download is counted + skipped; the op still returns Ok with a nonzero failed tally none (no backoff anywhere in connectors) n/a — stdout tally 'N downloaded, M failed' agent/lib/src/operations.rs
Agent archive creation (pack) yes — a source read failure is fatal for pack_directory_to_znippy transfer_to_* count download failures but a channel/tar-append failure is fatal none non-atomic (File::create directly, no temp+rename) — stdout tally / CLI error agent/lib/src/operations.rs
Agent upload (push) source-read fatal; decode over-cap short-circuits yes — per-asset upload failure counted (UploadOutcome::Failed); unsafe member path counted-skipped; Ok(()) with a nonzero tally none n/a — per-item ✓/✗ + '✅ N succeeded, M failed' agent/lib/src/operations.rs
Archive unpack unpack_tar_zstd: read_capped over-cap is fatal unpack_znippy: hostile path/extract failure counted, but the op BAILS at the end if failed>0 (the one push/pull that surfaces failure as an error) none non-atomic writes; safe_join blocks traversal — stdout tally + non-zero exit for unpack_znippy agent/lib/src/operations.rs
Archive conversion (znippy↔tar-zstd) yes on decode/read error staged through a tempdir none tempdir dropped — CLI error agent/lib/src/operations.rs
Repository fetch no — a miss is Ok((404,…)), never Err; backend errors are laundered to opaque 'internal error' n/a none n/a — 404 / opaque 500 (detail only in server log); quarantined → not_found server/lib/src/grpc.rs
Repository put over body-cap → 413; unauthorized → 401/403 pre-body no none atomic (temp+rename) for raw/file backends; writable-znippy append under write_lock — gRPC status / HTTP code server/lib/src/grpc.rs; server/lib/src/raw.rs
Promotion request-only; gates run BEFORE any write (advisory/property/license) a lineage-append failure AFTER the seal is non-fatal + logged loudly none an illegal promotion writes nothing; seal is create_new (exclusive) — 412 failed_precondition, audited; job ledger records 'failed' server/lib/src/promote/mod.rs
Withdrawal request-only no — appends a Withdrawn lineage entry; blob+ref untouched none append-only (OpenOptions::append) — state reduced from history replay server/lib/src/promote/stages.rs
Retention planning no — dry-run default plans without deleting reports candidates + reclaimed bytes none n/a (no writes in dry-run) — plan output; --execute required to delete server/lib/src/retention.rs
Retention execution no per-artifact delete; deletes historized as ident holger-retention-gc none sha256-verified delete on raw/file backends — audit Delete events + reclaimed bytes; job ledger 'retention-gc' server/lib/tests/retention_executor.rs
Replication no — per-artifact failures counted (Outcome::Failed) yes — is_complete() false; read-only dest → Failed fail-closed none; idempotent re-run (sha256 skip) dry-run default writes nothing — copied/skipped/would_copy/failed tallies; admin-gated RPC, audited server/lib/src/replication.rs
Airgap driver execution a non-zero driver exit is captured into the Report (the one shell-out) the driver has its own AIRGAP_DRY_RUN; --dry-run returns before driver discovery none n/a (external process) — captured stdout/stderr/exit in the Report airgap/src/lib.rs
UI remote connection no — connection error surfaced in the UI, not a crash n/a user-driven n/a — error state in the egui view (block_on on a private runtime) server/lib/src/object.rs; server/ui/src/data.rs

Trust and lifecycle

State transitions and which states affect detection/enforcement (implementation locations live in the invariants).

(no entries)

Human gates (propose vs act):

(no entries)

State and persistence

→ Persistence types: ai-context-full.md.

(no entries)

Change-impact map

Interface/type Defined in Implementers Main consumers Runtime significance Required tests
RepositoryBackendTrait traits/src/lib.rs:202 ~21 production (16 backend crates + group/proxy/raw/upstream_rust/upstream) + ~22 test mocks fast_routes, grpc + grpc/services, exposed/http, object.rs, search, replication, retention central backend contract; ADDING a defaulted method is safe, changing fetch/put/handle_http2_request breaks every backend at compile cargo test -p holger-server-lib
ConnectorTrait traits/src/lib.rs:404 Nexus / Artifactory / World connectors + a bench mock agent/lib operations.rs, agent/cli main.rs, UI tools pane, airgap driver the agent transfer contract; async; changing it touches every connector + the transfer matrix cargo test -p holger-agent-cli
HolgerObject traits/src/lib.rs:556 LocalHolger + RemoteHolger (object.rs) + FakeHolger (UI test) UI UiData (data.rs), holger-embed, holger-demo, server CLI the in-process vs gRPC facade the UI is built on cargo test -p holger-server-ui
ArtifactId / ArtifactFormat traits/src/lib.rs data types; ArtifactFormat has 15 variants, each with a backend; proto twin in generated/holger.v1.rs everything; from_format_str parsing, znippy_type_id() discriminants adding a format variant triggers non-exhaustive-match errors across backends + a proto round-trip test cargo test -p holger-server-lib
znippy read/format seam (FormatGate, ensure_supported_format, plugin views as_rust/as_maven/…) traits/src/znippy_format.rs gate consumed ONLY by znippy-rust; typed views per-backend (maven/npm/python/conda/gem/rust) all znippy backends open archives via znippy-common; writer seam used by znippy-rust dev-mode + agent pack any znippy API change breaks the whole workspace via [patch.crates-io] cargo test -p holger-rust-znippy-repository --test format_version_gate
AuditLog server/lib/src/audit/mod.rs Noop / Memory / ArrowIpc (arrow_ipc.rs) / Channel (UI lineage) grpc + grpc/services fetch/put/promote, exposed/http, replication the audit choke point; a new door that skips record_audit loses the audit trail cargo test -p holger-server-lib audit
AuthProvider seam (nornir_service, edda) server/lib/src/auth.rs bearer / oidc / oauth2 / mtls providers (webauthn declared, unwired) grpc.rs + exposed/http.rs validate_request; build-space tier link-audit changing the seam affects every tier's pinned provider set; fail-closed when unpinned cargo test -p holger-server-lib; cargo test -p holger-build-space --test auth_ship_small
PromotionEngine server/lib/src/promote/mod.rs single engine + stages.rs lineage grpc/services PromotionService, lib.rs startup wiring, job ledger seal-once + withdraw-is-append invariants live here; a File::create 'idempotence' change breaks immutability cargo test -p holger-server-lib promote
replication::replicate_pairs server/lib/src/replication.rs one pure engine over RepositoryBackendTrait grpc/services ReplicationService (admin-gated), CLI replicate verb sha256-idempotent, dry-run-default; the admin gate + dry-run default are the safety rails cargo test -p holger-server-lib replication
generated gRPC types (holger.v1) server/lib/src/generated/holger.v1.rs prost/tonic codegen (committed, not built at compile) grpc.rs conversions, every service, the RemoteHolger client edit proto/holger.proto then cargo xtask gen-proto; hand-editing the generated file drifts from the proto cargo test -p holger-server-lib
UI data/model contract (UiData, views) server/ui/src/data.rs UiData over HolgerObject; SearchView/SbomView/etc. app.rs scene/command, panes, tier features RepositoryInfo drives Browse off has_archive() — only znippy-rust overrides it, so most backends show empty Browse cargo test -p holger-server-ui
build variant types build-space/build-variants.toml build-space crate + tier features on server-lib/server-cli/holger-server-ui the compile_error! tier guard in server/cli/main.rs; auth_ship_small + tier_ladder audits tiers are mutually exclusive; a cross-tier feature edge or a forbidden dep (openssl/webauthn) fails the link-audits cargo test -p holger-build-space

Task-to-file map

→ Crate/module map: ai-context-full.md.

Task Start here Follow-up Invariants Fast test Full test Heavy/optional test
Add a repository backend / ecosystem server/lib/src/repository.rs, traits/src/lib.rs server/repository/znippy-rust/src/lib.rs, server/repository/file-rust/src/lib.rs, server/lib/src/exposed/http.rs no-traversal, format-version-pin cargo test -p holger-rust-znippy-repository --test format_version_gate cargo test -p holger-server-lib && cargo test -p holger-rust-znippy-repository —
Add / change a connector traits/src/lib.rs, agent/connector/nexus/src/lib.rs agent/lib/src/operations.rs, agent/cli/src/main.rs connector-same-origin, world-ssrf-allowlist, no-traversal cargo test -p holger-nexus-connector && cargo test -p holger-world-connector cargo test -p holger-agent-cli cargo test -p holger-agent-cli -- --ignored
Change agent transfer / partial-success agent/lib/src/operations.rs agent/cli/src/main.rs transfer-partial-success, no-traversal cargo test -p holger-agent-lib cargo test -p holger-agent-cli —
Change HTTP routing / native protocols server/lib/src/exposed/http.rs server/lib/src/lib.rs, server/repository/znippy-rust/src/lib.rs write-gate-fail-closed, read-gate-opt-in-fail-closed cargo test -p holger-server-lib --test integration_oci_tls cargo test -p holger-server-lib —
Change authentication / authorization server/lib/src/auth.rs server/lib/src/grpc.rs, server/lib/src/exposed/http.rs write-gate-fail-closed, read-gate-opt-in-fail-closed, tier-mutual-exclusion cargo test -p holger-server-lib auth cargo test -p holger-server-lib && cargo test -p holger-build-space --test auth_ship_small —
Change audit logging server/lib/src/audit/arrow_ipc.rs, server/lib/src/audit/mod.rs server/lib/src/grpc.rs, server/lib/src/exposed/http.rs audit-append-only cargo test -p holger-server-lib audit cargo test -p holger-server-lib —
Change promotion / retention server/lib/src/promote/mod.rs, server/lib/src/promote/stages.rs server/lib/src/grpc/services.rs, server/lib/src/lib.rs promote-seal-once, withdraw-is-append cargo test -p holger-server-lib promote cargo test -p holger-server-lib && cargo test -p holger-server-lib --test retention_executor —
Change replication server/lib/src/replication.rs server/lib/src/grpc/services.rs, server/cli/src/main.rs write-gate-fail-closed cargo test -p holger-server-lib replication cargo test -p holger-server-lib —
Change airgap behavior airgap/src/lib.rs server/cli/src/main.rs — cargo test -p holger-airgap — —
Change the proto / gRPC surface proto/holger.proto server/lib/src/generated/holger.v1.rs, server/lib/src/grpc.rs — cargo test -p holger-server-lib cargo xtask gen-proto && cargo test -p holger-server-lib —
Change the UI server/ui/src/data.rs, server/ui/src/main.rs server/lib/src/object.rs, server/ui/tests/tier_ladder.rs tier-mutual-exclusion cargo test -p holger-server-ui cargo test -p holger-server-ui --features gui cargo test -p holger-server-ui --test tier_ladder -- --ignored
Change a deployment tier / build variant build-space/build-variants.toml, server/cli/src/main.rs server/lib/Cargo.toml, server/ui/Cargo.toml tier-mutual-exclusion cargo test -p holger-build-space — cargo test -p holger-server-ui --test tier_ladder -- --ignored

CLI inventory

→ CLI command enums / parsers: ai-context-full.md.

Binary Command/subcommand Important flags Calls into Runtime/feature restrictions Tests
holger-server start --config server/lib/src/lib.rs tier features embedded/mini/full (mutually exclusive) cargo test -p holger --test tools_robot
holger-server dev-pair --data [--grpc 127.0.0.1:18443] [--http 127.0.0.1:18444] server/lib/src/lib.rs dev convenience — autogen RON + sparring archives —
holger-server install / service add|update|remove|status|render; service install|start|stop|status|health|uninstall --scope user|system server/cli/src/main.rs none —
holger-server retention --config [--repo --keep-last-n --max-age-secs --max-total-bytes --protect-property k=v --execute] server/lib/src/retention.rs dry-run default; --execute deletes cargo test -p holger-server-lib --test retention_executor
holger-server replicate --from --to [--repo] [--execute] [--limit] server/lib/src/replication.rs dry-run default; pairs repos by identical name —
holger-server airgap --stack --scale micro|small|large --target holger|nexus|artifactory --auth mtls [--dry-run --build-only --push-only] airgap/src/lib.rs --dry-run returns before driver discovery; needs distribution/airgap/populate on disk —
holger-server search / sbom / properties / scan / holger-ds search --name/--sha256/--property; sbom attach|fetch; properties set|get; holger-ds --scale/--preset (bench) server/lib/src/lib.rs holger-ds requires bench (omitted by embedded/mini) —
holger-agent (single verb) --from world|nexus|artifactory|holger|znippy|tar-zst|directory --to znippy|tar-zst|directory|nexus|artifactory|holger --lockfile-kind cargo|requirements-txt|uv-lock --auth oidc|mtls agent/lib/src/operations.rs same-type + identity + world→server combos bail (airgap first) cargo test -p holger-agent-cli
holger-server-ui (positional ENDPOINT) ENDPOINT (default http://127.0.0.1:50051) --token --ca --cert --key [--demo (demo feature)] [--robot-remote (robot-remote feature)] server/ui/src/main.rs gui/gui-mini/gui-mikro tiers; --demo needs demo; tier binaries: holger-server-ui, holger-server-ui-mini, holger-server-ui-mikro cargo test -p holger-server-ui
holger-mannequin serve / pki generate [--gui] one-port mock PKI+OIDC+Nexus mannequin/src/main.rs TEST/DEV ONLY — never production cargo test -p holger-mannequin
xtask gen-proto / demo-wasm / deploy-pages gen-proto (protox+tonic-build, pure Rust) xtask/src/main.rs dev tooling; gen-proto output is committed cargo test -p xtask --test holger_matrix

Configuration and environment

→ Config structs: ai-context-full.md.

Main config (secrets never appear in the config file — see environment):

Config field Type Default Required Sensitive Runtime consumer Reloadable Validation
repositories[].ron_name String — Yes — server/lib/src/repository.rs startup-only —
repositories[].ron_repo_type String (rust|rust-remote|maven3|pip|npm|nuget|go|gem|deb|rpm|helm|docker|oci|raw|conda|composer|group|znippy|{nexus,artifactory,azure,aws}-remote) — Yes — server/lib/src/repository.rs startup-only *-remote need federation/online; unknown value = boot error
repositories[].ron_upstreams Vec [] Yes — server/lib/src/lib.rs startup-only proxy chain, first-hit-wins; makes the repo a ProxyBackend
repositories[].ron_archive_path Option — No — server/lib/src/repository.rs startup-only read-only znippy snapshot (required by znippy_backed! backends)
repositories[].ron_writable_archive Option — No — server/repository/znippy-rust/src/lib.rs startup-only rust only; live seal-able archive; wins over archive_path/data_dir
repositories[].ron_data_dir Option /var/lib/holger/{ron_name} No — server/repository/file-rust/src/lib.rs startup-only file/oci/raw backends
repositories[].ron_trusted_roots Option (DER root) — No No server/lib/src/repository.rs startup-only znippy CMS provenance verify; needs sign, else wiring fails closed
exposed_endpoints[].ron_url String (SocketAddr) — Yes — server/lib/src/lib.rs startup-only the gRPC listen address
exposed_endpoints[].ron_http_url Option — No — server/lib/src/exposed/http.rs startup-only enables the native-protocol HTTP/OCI door + /healthz /readyz
exposed_endpoints[].ron_tls Option<{cert, key, client_ca}> — No Yes server/lib/src/exposed/tls.rs startup-only client_ca present ⇒ mTLS; absent ⇒ cleartext (loud warning)
exposed_endpoints[].ron_max_body_bytes Option 1 GiB No — server/lib/src/grpc.rs startup-only shared gRPC message + HTTP body cap
auth.methods[] Vec<Oidc|OAuth2|Mtls|StaticToken> — No Yes server/lib/src/auth.rs startup-only provider must be feature-pinned by the tier or it authenticates nothing (fail-closed)
auth.roles / default_role / repo_roles Map<subject,Role> (Denied|Reader|Writer|Admin) {} (RBAC off) No — server/lib/src/auth.rs startup-only empty + no default_role ⇒ RBAC disabled; set ⇒ fail-closed writes
auth.require_auth_for_reads bool false No — server/lib/src/auth.rs startup-only opt-in read gate (fail-closed); health stays open
audit.dir / audit.enabled String / bool holger-audit (under $HOLGER_DATA) / true No — server/lib/src/audit/arrow_ipc.rs startup-only relative dir anchors under $HOLGER_DATA; absolute wins

Environment variables:

Variable Category Default Sensitive Runtime/build/test Consumer Effect
HOLGER_DATA path — — runtime server/lib/src/lib.rs; server/ui/src/data.rs anchors relative audit dir; derives data_root for prod store / job ledger / sboms / props
HOLGER_TOKEN secret — Yes runtime server/ui/src/main.rs UI bearer token (alt to --token)
HOLGER_PROMOTE_ENFORCE enum (block|warn|off) — — runtime server/lib/src/promote/mod.rs promote policy enforcement; a bad value FAILS startup
HOLGER_PROMOTE_ADVISORY_DB path — — runtime server/lib/src/promote/mod.rs OSV/GHSA snapshot for promote gating; unreadable ⇒ serve refuses to start
HOLGER_PROMOTE_DENY_LICENSES csv — — runtime server/lib/src/promote/mod.rs SPDX ids denied at promote
HOLGER_PROMOTE_REQUIRE_PROPERTY k=v — — runtime server/lib/src/lib.rs RETIRED (2026-10-22): set ⇒ the promotion engine is refused; the requirement is promotion.require_property in the RON config
HOLGER_QUARANTINE_PROPERTY k=v — — runtime server/lib/src/lib.rs RETIRED (W6): set ⇒ the boot is refused with holger-intake-14; the one hold is the intake chain's QUARANTINED state (holger-server intake quarantine)
HOLGER_WAREHOUSE path — — runtime server/ui/src/data.rs opens the skade/modgunn Iceberg advisory warehouse in-process (warehouse-scan only)
HOLGER_DEMO_BEAT_MS int 2200 — dev server/ui/src/main.rs demo robot beat interval
HOLGER_DEMO_IMAGE string docker.io/library/registry:2.8.3 — dev server/ui/src/infra.rs demo appliance image
HOLGER_BENCH_DIR path $HOME/holger-bench-data — bench bench-scenarios/src/lib.rs bench fixture root
HOLGER_BENCH_SCALE enum (quick|full|huge) — — bench bench-scenarios/src/lib.rs bench preset; NORNIR_BENCH_QUICK=0 forces full
HOLGER_REQUIRE_NETWORK flag — — test agent/cli/tests/integration_pip.rs test-only: convert offline skip into a failure
HOLGER_BENCH_BYTES int — — bench bench-scenarios/src/lib.rs override synthetic artifact size
HOLGER_BENCH_ENTRIES int — — bench bench-scenarios/src/lib.rs override synthetic entry count
HOLGER_BENCH_SECS int — — bench bench-scenarios/src/lib.rs override bench run duration
HOLGER_BENCH_REAL flag (REAL=0 opts out) — — bench bench-scenarios/src/lib.rs use synthetic instead of real-artifact corpus
HOLGER_BENCH_BOOT_SECS int — — bench bench-scenarios/src/lib.rs JVM/container boot timeout for competitor twins
NORNIR_BENCH_QUICK flag (=0 forces full) — — bench bench-scenarios/src/lib.rs back-compat scale toggle
DOCKER_HOST url — — test bench-scenarios/src/lib.rs testcontainers socket autodetect (rootless podman)
PIP_PROBE_MB int — — bench bench-scenarios/examples/pip_probe.rs pip probe example sizing
PIP_PROBE_SECS int — — bench bench-scenarios/examples/pip_probe.rs pip probe example duration

Precedence (verified):

  1. Compiled defaults (constants: 1 GiB body cap, ports, page sizes).
  2. RON configuration file (--config): the authoritative graph of repositories/endpoints/auth/audit.
  3. Environment variables read at startup (e.g. HOLGER_DATA anchors audit/store paths; HOLGER_PROMOTE_* tune the promote engine) — override compiled defaults, NOT RON fields (they configure orthogonal knobs, not the same keys). Verified: no env var overrides a RON field.
  4. CLI arguments to the specific verb (e.g. holger-server-ui ENDPOINT/--token override HOLGER_TOKEN; holger-server retention/replicate/airgap flags). Unverified where a flag and an env var name the same knob — treat as Unverified.

Dependencies (full workspace graph + external hotspots are in the full pack):

42 workspace crates, 99 deduplicated workspace edges, 95 external dependencies. External hotspots and full coupling are in ai-context-full.md.

High-coupling crate Incoming Outgoing
holger-traits 28 0
holger-server-lib 5 22
holger-bench-scenarios 4 7
holger-agent-cli 0 8
holger-plugin-host 3 5
holger-agent-lib 3 4
holger-rust-znippy-repository 4 3
holger-server-ui 0 7

Tests inventory

Derived totals: 658 unit test fns across 91 files, 239 integration test fns.

Incomplete work and current limitations

Marker classification Count
false-positive 7
heavy-test 24

→ Full marker list: ai-context-full.md.

Documentation and wiring mismatches

None: no status contradictions, and every declared invariant has enforcement locations.

Size and generation metadata

Measured for the compact pack (excludes this footer):

Metric Value
Bytes 54112
Lines 399
Words 7085
Approx tokens (chars/4) 13528
Token budget 45% of 30000
Duplicate-sentence ratio 0.000 (max 0.040)
Duplicate-row ratio 0.000 (max 0.020)