holger — the stinky list, 2026-09-20
Five lanes read holger against what the owner said it must be: "en mycket performant, säker produkt, zero copy, apache arrow och produkt som överlever, har job köer", an appliance with no OS, a twin beside it, and a last MVP feature that is holger in the cloud with repo A→B, then holger on-prem with repo C. Everything that does not fit becomes holger Pro.
Each section is measured or labelled. Read the section for the argument; this page is the map and the four things that are true across all five.
| § | section | branch |
| 01 | the data plane — zero copy, Arrow, gatling, job queues, survival | stinky/dataplane |
| 02 | the twin, growth, and the A→B→C question | stinky/twin |
| 03 | security, attestation, and the auth bloat question | stinky/security |
| 04 | code structure — traits, seams, the wasm plugin ABI | stinky/structure |
| 05 | the suite, monetize, and the MVP line | stinky/suite |
| 13 | the owner's requirements checked against the code, 2026-09-22 (twin, one ISO, election, WireGuard + mTLS, mock) | agent/v1-requirements-2026-09-22 |
1. THE FOUR THINGS THAT ARE TRUE ACROSS ALL FIVE
1.1 One dependency edge has been deciding what ships, three times
holger-bench-scenarios pins holger-server-lib with default features, and cargo unifies features per GRAPH. So a benchmark crate has silently decided the contents of the production binary three separate times:
| what leaked in | found | how |
front |
6f02ddf, 2026-09-17 |
the public web page compiled into binaries built with bench |
auth-passkey |
2026-09-20 | the appliance passkey door existed BY ACCIDENT; default never named it |
openssl-sys + auth-oidc + auth-oauth2 |
2026-09-20 | native OpenSSL reaches the systemd binary through reqwest 0.12 → hyper-tls → native-tls |
The third is the serious one: holger-server links native OpenSSL through a benchmark crate, and default-tls lands on the production OIDC/JWKS fetch path. The recipe exempts that row from the egress judge, so nothing caught it. holger-server and holger-full link the IDENTICAL 431-crate set — comm -3 is empty — and 21 of those crates arrive through this one edge.
The pattern, not the three bugs: a feature is only honest when a VARIANT names it. sign is named by no variant at all and rides a dep edge into every tier. The fix is a rule, not three patches: every feature in a shipped artifact must be named by the row, and a dev/bench dependency must carry default-features = false.
1.2 A great deal is built and nothing drives it
| built | driver |
attest.rs, 1 458 lines, DSSE + in-toto, digest-keyed |
no door, no constructor, no consumer |
ReplicationService.Replicate, idempotent by content id |
nothing on a schedule; no twin role — ✅ 2026-10-22 (SA, dc801dff): an executing Replicate on a server with a job queue runs as a resumable Job::Replicate; a dry run stays the inline preview |
PromotionEngine::fetch |
no non-test caller — ✅ 2026-10-22 (SA, c9031dac): it IS the download gate's read (IntakeChain::serve → sealed_bytes, HTTP door exposed/http.rs), proven by the_download_gate_reads_through_the_engine_and_refuses_a_tampered_seal |
Pool::delta_since / PoolDelta in Skidbladnir |
callers are #[cfg(test)] only — ✅ DELETED 2026-10-22 (SA, Skidbladnir 67a3da2): holger never used the pool; its twin follows by manifest + watermark |
mirror_into |
zero callers — ✅ DELETED 2026-10-22 (SA, 6b7a3c12): the upstream mirror is the mirror cycle (Job::MirrorAsset) |
| wasm plugin host, bounded and CMS-verified | wasm-plugins OFF in every tier |
| metering doors, ~2 300 lines, twice | two unmerged branches, one hour apart |
This is the house's real debt: not missing code, but code with no seam to the outside. promote/mod.rs:123 still calls modgunn with blob_sha256: "" while the digest-carrying seam sits one layer below with no callers.
1.3 The product does not yet behave like a warehouse under load
Everything moves as a whole Vec<u8>, at every hop: fetch, put, the HTTP body collected to 1 GiB, sha256 on the request thread at 0.61 GB/s, Full responses, and a gRPC "stream" that loads the whole blob first. There is no job queue — promote, replicate, upload and gate all run inline on the RPC, nothing resumes after a restart, and running rows are permanent once the process dies.
And the engine the law names is linked but unused: rotaryengine (gatling), znippy-zoomies, lgz and ldeflate are each linked twice (crates.io and path) (✅ rotaryengine: ONE copy since 2026-10-22, SA fc62ab24 + draupnir f6eaded), while holger-server calls the engine zero times. rayon is absent in all four tiers — the one clean row.
1.4 The MVP is not slim, and slimness is its whole point — DONE 2026-09-21
Struck 2026-09-21 (agent/slim-mvp): marketplace is a feature named by full + server-local; every backend is a fmt-* feature, the MVP rows name fmt-raw + fmt-rust, a RON naming any other format refuses boot as holger-mvp-1. holger-mvp 463 → 442 crates (liblzma-sys gone). Table and reasoning: .nornir/HOLGER-PLAN.md §4 "stinky 1.4 — MVP slimmed 2026-09-21". The holger-remote half of this row was answered separately (?1, C pulls). What stood here:
pub mod marketplace has no feature gate at all: ~2 400 lines of Microsoft fulfilment and tenant provisioning are compiled into holger-mvp, the airgap appliance. All 17 repository backends are unconditional. Meanwhile the thing the owner actually called the last MVP feature — a downstream holger that can reach an upstream one — does not exist: holger-remote returns 0 grep hits.
2. THE DECISION THAT BLOCKS THE LAST MVP FEATURE
Both halves of holger's own design document are written, and they contradict each other. This is the owner's to settle, and nothing downstream of it should be built first.
| carried bundle | holger-remote pull |
|
| zero-egress guarantee | intact and true | the guard measures the wrong thing — the tonic client is exempt and is already in every tier |
| does upstream know what C has? | no | yes — AuditEvent{action: Download} grouped by ident |
| what the UI may say | bundled | sent — never accepted |
| prerequisite | audited export under C's principal | C needs an identity and a route upstream |
⚠ Either way, the audit log has no query surface (AuditLog = record + flush, no RPC, no route). The ruling "the upstream already knows, because it served the fetch" cannot be honoured until that exists.
3. THE QUESTIONS THE OWNER ASKED, ANSWERED
3.1 "Har holger blivit bloated av passkey?"
No. Measured in bytes, not argued: holger-mvp 32 475 776 B without auth-passkey, 32 875 720 B with — +399 944 B, +1.2 %, four crates, all pure Rust (nornir-auth-webauthn, ciborium, ciborium-io, ciborium-ll). reqwest, ureq, openssl-sys, webauthn-rs and wasmtime are all absent from the appliance. The door 404s by name when unconfigured and a bad rp_id refuses BOOT. Two planes are respected: the appliance's direct airgap login and the console's BFF.
The bloat is elsewhere — see §1.1. holger-server default is 39 251 408 B, 6.8 MiB above the appliance.
3.2 "Kan gunnar och holger samarbeta, ax till limpa?"
Yes, and the premise needs one correction: gunnar does not use DSSE at all (SLSA fields in an sshsig envelope). So the shared answer is not gunnar's envelope — it is znippy's, which both should adopt. Home: a new edda crate depending on znippy-common/sign and nornir-build-thing, with gunnar and holger both depending on it and neither on the other.
The house's embarrassment, measured: four WebAuthn verifiers (edda's pure one, plus garmr → gunnar-ui-server → holger-ui-server hand-ported) and three key identity vocabularies, so a customer needs three trust stores today — cert fingerprint, SSH fingerprint, raw ed25519 key.
3.3 "Twin som gunnars — och kan mönstret bära A→B→C?"
One engine, two drivers. replicate_pairs is a pure function over two backends and should serve both. Above it they are different types: the twin copies the whole data root — secrets and KEK included — ungated, measured by a watermark; C copies one promoted repo through AttestationGate=Block and can only ever be sent. A single Peer{direction} would force a gate onto the twin (no longer a restore source) or the KEK onto C (a boundary breach).
Both PULL. gunnar proved pull, the primary dials nobody, and the C-knows ruling only holds if C's fetches land as Download rows under C's own ident — a push produces Upload rows written by the driver. What private-holger-ops/estate.toml:364-365 currently owes is a PUSH from the primary: wrong direction, and it makes the ruling false.
⚠ And today replicate_pairs cannot make a twin: it moves artifacts by coordinate only — no evidence store, no audit segments, no properties, no config, no bundle store. Promoted, such a twin serves artifacts with no provenance. That is gunnar's un-metered-twin scar in a new coat.
3.4 "En holger-plugin för monetize?"
Not a plugin — a native seam. The wasm ABI is a package-handler ABI: no principal on the wire, no digest in WireArtifactId, no audit or clock access, one implicit ungrantable capability, and an exact-match u32 version whose manifest decoder rejects unknown fields, so it cannot even negotiate. Of the three plausible plugins, today's ABI carries only "another format handler".
What monetize actually needs is the gRPC door that is already written twice and merged never. And a cap over a quota is a gate, which needs the trait holger does not have.
3.5 "Är koden trait-baserad?"
More than expected — RepositoryBackendTrait has 12 verbs and 21 implementations — except where it decides the product. There is no Gate trait: policy is a struct configured by ENVIRONMENT VARIABLES on a box with no shell, Ok(None) means pass-through, and judgement is per artifact by name@version with no set-scope. gunnar has the seam (gunnar-policy/src/lib.rs:173) and the law that goes with it — GUN-007: an unreadable gate config refuses everything, because an unreadable policy defaulting quietly once turned branch protection off.
✅ 2026-10-22 (SA, 95043a69): decided by the code — the stub is DELETED, not grown into a trait. The one factory that opens bytes is Repository::backend_from_config_with, and Repository::storage_places answers where they are; a storage endpoint is a name the wiring checks. What stood: Storage is worse: backend_from_config returns Ok(()). There is no storage seam at all; 17 backends each open their own bytes.
4. WHAT TO DO FIRST
- Settle §2. Building before it builds the wrong product with a green guard.
- Close the bench edge (§1.1) and add the rule that a shipped feature must be named by its variant.
- A
Gatetrait over aPromotionContextcarrying the SET with digests required — it deletes the env parsing, so it is net-negative surface and fits MVP. - Fix the promotion that lies: a promotion to a non-existent repo SUCCEEDS, and a test asserts it (
promote/mod.rs:812-826). - An audit query surface, without which §2 cannot be honoured either way.
- Feature-gate
marketplace— onecfgshaped likefront, −2 400 lines from the airgap appliance. - Then performance:
HotArchivefor the writable znippy repo, a streaming contract instead ofVec<u8>, and gatling where the server does the work.