# Gunnar — the full documentation

> Gunnar is a git server written in Rust: SSH and smart HTTP, Git LFS, signed pushes, geo-replication to a twin, and an OS-less appliance. Free to host yourself; the same code is hosted at gunnar.rs.

# What Gunnar is

Gunnar is a git server written in Rust. It serves git over SSH and over smart
HTTP, and it stores repositories in one of two engines: znippy (Apache Arrow
IPC on a single machine) or gix (a traditional file system layout).

- Git over SSH and smart HTTP, with Git LFS.
- Pushes signed with sshsig (ed25519), a transparency log and SLSA-shaped provenance.
- Built-in geo-replication to a twin Gunnar.
- An OS-less appliance variant with a hardened kernel, WireGuard between the boxes and NVMe storage.
- Webhooks, OCI, and Prometheus metrics.
- A web console: sign in with a passkey, browse repositories, review pull requests and manage access.
- Free to host yourself under AGPL-3.0-only. The same code is hosted at https://gunnar.rs.

Gunnar is made by Vetra Automation AB and Ignalina ApS. Its source is the
public repository `nordisk/gunnar` on gunnar.rs, and the web console's source
is `nordisk/gunnar-ui`.

# Using Gunnar

## Read a public repository

Every public repository on gunnar.rs can be read without an account:

- `https://gunnar.rs/<namespace>/<repository>` is its page: the README, the files and the newest commits.
- `https://gunnar.rs/<namespace>/<repository>/readme.md` is its README as Markdown.
- `https://gunnar.rs/<namespace>/<repository>/tree.txt` is its file tree as plain text.
- `https://gunnar.rs/llms.txt` lists every public repository.

## Clone

Gunnar serves git over SSH on port 2222:

```
git clone ssh://git.gunnar.rs:2222/<namespace>/<repository>
```

A public repository clones with no account. Pushing needs an account and an
SSH key registered on it.

## Build and run your own Gunnar

Gunnar is a Rust program, and a lone clone builds the server by itself:

```
git clone ssh://git.gunnar.rs:2222/nordisk/gunnar
cd gunnar
cargo build --release
./target/release/gunnar
```

A bare `gunnar` serves on loopback with no configuration: SSH on
`127.0.0.1:2222`, smart HTTP on `127.0.0.1:8080`, and its health at
`http://127.0.0.1:8080/healthz`. `cargo install gunnar` installs the same
server.

## Sign in

The web console signs people in with a passkey, not a password. The
command-line client signs in with `gunnar login`, which a person approves in
the console.

# Public repositories

## mikronova/draupnir

At https://gunnar.rs/mikronova/draupnir.

### README.md

#### draupnir

The **boot / provisioning engine** of the **nordisk** constellation — named for
**Draupnir**, Odin's gold ring that drips eight identical copies of itself every
ninth night.

One lean **Rust** library, one `BootSpec`, **three boot backends**: it fires up a
runtime instance and drives its power lifecycle, whether that instance is a KVM
virtual machine, an OCI container (a redis service, say), or a **bare-metal
server** provisioned out-of-band. Draupnir's own code is the thin unifying seam —
each backend *delegates* to an existing engine (Brokkr's minimal-dependency law:
reuse, don't reinvent).

---

##### The three backends

- **KVM** — an appliance VM, booted by driving
  **[tunnr](https://git.gunnar.rs/mikronova/tunnr)**'s QEMU + OVMF (UEFI) + KVM
  primitive (`BootSpec` / `boot_test` / `BootHandle`). Draupnir does **not**
  reimplement VM boot.
- **Container** — an OCI container over a container runtime (podman/Docker REST).
- **Redfish** — the capability Draupnir uniquely owns: boot **bare metal** through
  a server's BMC (iLO / iDRAC / OpenBMC) over the DMTF **Redfish** REST API —
  insert a virtual-media ISO, set the one-time boot override to it, power the node
  on. No OS on the box required.

```rust
use draupnir::{BootSpec, Boot, container::ContainerBoot};

let spec = BootSpec::container("cache", "docker.io/library/redis:7");
let redis = ContainerBoot::new().boot(&spec)?;   // a redis service, fired up
```

##### The ring drips eight copies

Draupnir's namesake dripped eight identical rings every ninth night. Here that is a
*natural extension, not the core*: `plan_fleet(&spec, 8)` fans one `BootSpec` out
into eight identically-payloaded, distinctly-named specs — **a fleet of identical
machines booted from one ISO** (bare-metal fleet provisioning via Redfish). The
core stays simple: one library, three boot backends.

##### Where it sits in the stack

Draupnir is the **shared low-level boot lib**. Two higher-level tools depend on it
**directly** and call *down* into it — neither reimplements boot code:

- **[Skidbladnir](https://git.gunnar.rs/nordisk/Skidbladnir)** — the service /
  systemd / airgap layer. It now **delegates instance-firing to Draupnir** and
  keeps only the high-level service & orchestration logic.
- **jera** (edda's job handler) — stays thin (a job is `process | VM | container`)
  and reaches booting straight through Draupnir.

```text
  jera ─────────┐
                ├──▶ draupnir ──▶ tunnr        (KVM VM)
  Skidbladnir ──┘             ├─▶ OCI runtime  (container / redis)
                              └─▶ Redfish BMC  (bare-metal ISO boot)
```

One home per capability: instance *booting* lives only in Draupnir; tunnr is the
sole KVM boot primitive, reached only through Draupnir's KVM backend.

##### Honest by construction

The trait surface, the `BootSpec`/`Machine` types, spec validation and the fleet
fan-out are **real and unit-tested**. Every unimplemented backend internal is an
honest `todo!("draupnir: …")` — never a fake-green stub. The default build is
**pure std, zero dependencies**; each live backend sits behind a feature.

```console
$ cargo build          # pure-std, green
$ cargo test           # spec + fleet bookkeeping
$ cargo build --features backend-tunnr   # live KVM boot via tunnr
```

##### Docs

`.nornir/` is the source of the docs: **[design](.nornir/design.md)** (the
ownership table + extraction plan) · **[guide](.nornir/guide.md)** (usage) ·
**[for-idiots](.nornir/for-idiots.md)** (the beginner walk-through).

---

Every crate in the constellation is held to
**[Brokkr's Software Criteria](brokkr-software-criteria.md)** — baremetal,
minimal-dependency, zero-copy / multi-core / SIMD.


## mikronova/funnel-core

At https://gunnar.rs/mikronova/funnel-core.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

#### funnel-core

> The pure workflow-DAG core of the nornir funnel — events, ids, state,
> topological readiness, history, the Plan-DAG projection, and the kanban-card
> board — with **no** warehouse / iceberg / model / GPU coupling.

Extracted from `nornir/src/funnel/` so both **nornir** (the viz funnel + Plan-DAG
panels) and **ordning-funnel** (idea → case graduation) share one idea→plan→node
DAG without dragging in nornir's storage stack. Everything here is deterministic:
given the same events + query string the output is bit-identical, so every surface
is golden-testable with no warehouse, no clock and no GPU.

<!-- Write any intro prose here. Anything outside the marker blocks below
     is preserved verbatim on every render. -->

##### The shape

A funnel is a fold of an append-only [`Event`] log into an in-memory [`Funnel`]:

```text
Event log ──apply──▶ Funnel ──┬─▶ history()          → HistoryItem[]  (intake list)
                              ├─▶ cards_from_funnel() → Card[]         → board() → Board (kanban)
                              ├─▶ query_cards(q)      → Card[]         (YouTrack-style smart query)
                              ├─▶ topo_ready()        → NextStep[]     ("what should I work on next?")
                              ├─▶ simulate_flow()     → FlowSim        (time-step the DAG to done)
                              └─▶ PlanDag::from_funnel → PlanDag       (nodes + edges + topo, viz)
```

Every projection is pure (`&Funnel → value`) and BTree-ordered, so the CLI, the
gRPC server and the viz all render the same bytes and never disagree.

##### Benchmarks

<!-- nornir:gen:start:benches -->
**v0.1.3 · oden · 32 cores · 2026-07-22**

| workload | assemble_ms | assemblies_per_sec | build_ms | cards | cards_per_sec | cards_scanned | cards_scanned_per_sec | critical_path_len | depth_nodes | edge_births | edges | elapsed_ms | events_per_iter | events_per_sec | hits | iters | node_births | nodes | pass_ms | passes_per_sec | plans | replay_ms | speedup_x | steps | stream_ms | timeline_ms |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| funnel-core.board_project | | | | 5200 | 639440 | | | | | | | 1626 | | | | 200 | | | | | | | | | | |
| funnel-core.funnel_build | | | **60.28** | | | | | | | | 4400 | 1206 | 10400 | 172531 | | 20 | | | | | 400 | | | | | |
| funnel-core.plan_dag_critical | | | | | | | | 12 | 5200 | | 9200 | 3086 | | | | 200 | | 5200 | **15.43** | 65 | | | | | | |
| funnel-core.plan_dag_growth | | | | | | | | | | 599 | | 10737 | | 2803 | | 50 | 301 | | | | | | | 602 | | **214.74** |
| funnel-core.plan_dag_growth_stream | | | | | | | | | | 599 | | | | 2683 | | 50 | 301 | | | | | 47437 | 211.41 | 602 | **224.38** | |
| funnel-core.plan_dag_whole | **17.06** | 59 | | | | | | | | | 9200 | 1706 | | | | 100 | | 5200 | | | | | | | | |
| funnel-core.smart_query | | | | | | 5200 | 3.07M | | | | | 847 | | | 2403 | 500 | | | | | | | | | | |
<!-- nornir:gen:end:benches -->

The board / smart-query projection (`cards_from_funnel` → `board` → `query_cards`)
and `PlanDag::from_funnel` are the hot paths the funnel UI runs on every render;
they are benched under `bench/` (`nornir bench run funnel-core`) and, for the
Plan-DAG assembly, by nornir's own `nornir.plan_dag_assemble` arm.

##### Tests

<!-- Unit / integration / doc tests, enumerated from source via syn. -->
<!-- nornir:gen:start:tests -->
**Unit tests** (51)

| file | tests |
|---|---|
| `src/card.rs` | `card_projection_maps_facets_and_labels`, `hashtag_mining`, `smart_query_operators`, `smart_query_sort`, `board_groups_into_fixed_lanes`, `empty_funnel_is_honest_empty_board`, `cards_from_funnel_includes_task_nodes`, `card_state_changed_and_assigned_are_reflected_in_the_projection`, `smart_query_assignee_and_for` |
| `src/history.rs` | `history_derives_kind_status_and_filters` |
| `src/mode.rs` | `tokens_round_trip_and_are_stable`, `parse_is_forgiving_and_case_insensitive`, `mode_gate_maps_to_the_right_lane`, `default_is_auto` |
| `src/plan_dag.rs` | `assembles_mother_and_dependency_edges`, `topo_order_puts_dependencies_before_dependents`, `scoping_to_a_plan_keeps_its_mother_and_children`, `state_json_is_deterministic_and_shaped`, `empty_funnel_is_an_empty_graph`, `slack_grades_tension_and_critical_nodes_are_zero_slack`, `critical_nodes_is_a_superset_of_the_single_critical_path`, `bottlenecks_rank_by_downstream_reach_not_local_fanout`, `layers_group_nodes_by_depth_and_width_is_the_peak`, `critical_path_deps_is_strictly_shorter_than_the_mother_lineage`, `critical_path_deps_picks_the_longer_dep_branch_ignoring_mother`, `depths_layer_dependencies_before_dependents`, `critical_path_is_the_longest_chain`, `critical_path_picks_the_longer_of_two_branches`, `state_json_carries_the_dag_vocabulary`, `diff_classifies_added_removed_and_changed`, `identical_revision_diffs_to_empty`, `growth_stamps_first_appearance_of_each_node_and_edge`, `growth_skips_rejected_events_and_scopes_like_from_funnel`, `extend_streams_byte_identical_births_to_the_batch_fold`, `extend_matches_batch_scoped_and_skips_rejected_events` |
| `src/state.rs` | `edge_closing_a_cycle_is_rejected`, `transitive_and_direct_back_edges_are_both_rejected`, `plan_still_drains_after_a_cycle_edge_is_refused`, `valid_run_bumps_high_water_and_never_touches_node_status`, `high_water_is_monotone_not_last_write`, `unknown_plan_or_node_is_rejected_and_high_water_unchanged`, `runs_are_retained_in_log_order_with_every_payload_field`, `duplicate_run_id_is_rejected`, `the_same_node_id_in_a_second_plan_is_rejected`, `distinct_ids_still_land_and_same_plan_dup_keeps_its_message`, `non_seq_ids_are_still_unique_funnel_wide`, `card_assignment_stays_unambiguous`, `folding_the_same_log_twice_yields_identical_state`, `last_write_wins_fields_follow_log_order` |
| `src/topo.rs` | `all_pending_reaches_all_done_in_chain_length_waves`, `a_failed_first_node_stalls_its_dependents` |

**Integration tests** (12)

| file | tests |
|---|---|
| `tests/public_api.rs` | `full_projection_pipeline_composes_via_reexports`, `smart_query_operators_compose`, `topo_ready_and_simulate_flow_via_reexports`, `plan_dag_projection_via_reexports`, `wire_tokens_are_stable`, `dag_wire_tokens_are_stable`, `idea_public_field_contract`, `funnel_mode_vocabulary_via_reexports`, `plan_dag_critical_path_via_reexports`, `plan_dag_diff_via_reexports`, `plan_dag_growth_timeline_via_reexports`, `apply_rejects_invariant_violations` |

**Doc tests** (0)

_(none)_
<!-- nornir:gen:end:tests -->

##### Dependency graph

<!-- nornir:gen:start:depgraph -->
![workspace dependency graph](.nornir/assets/depgraph.svg)
<!-- nornir:gen:end:depgraph -->

##### License

MIT OR Apache-2.0.


## mikronova/kyokkou

At https://gunnar.rs/mikronova/kyokkou.

### README.md

#### kyokkou (極光)

> 極光 — polar light. The aurora. Light created at the extremes.

**Stack:** Rust · egui · egui_graph · Blender 5.1.1 · FalkorDB

> *Intent in. Raytraced beauty out.*

---

##### Sibling projects — shared hardware, independent stacks

All three projects run on the same machine (ThinkPad T14s AMD 360 · gfx1150 · 32 GB shared VRAM).
Each project manages its own ROCm stack independently — no project depends on another.

| Project | ROCm strategy |
|---|---|
| text-analysis | lemonade-sdk bundled ROCm 7 — fully self-contained |
| klient-t14s | installs ROCm + HIP system-wide — self-contained installer |
| kyokkou | ROCm + HIP inside container — fully isolated |

---

##### What it is

An intent-driven visual generator. You pick what you want to create.
The tool handles ALL Blender settings — materials, lighting, sim params,
camera, render quality. One button: **Create image** or **Create animation**.

You never touch Blender directly.

---

##### Origin

> *"I want great Blender creations — raytraced god rays, metal and water reflections,
> fluid animations through pipes, 3D system maps with information flowing as fluid.
> A Blender setup acting as Grafana, or a render export feeding my Rust egui engine
> with live system metrics."*

---

##### Architecture

```
Rust egui UI (kyokkou)
    → validated scene_config.yaml
        → bpy scene script (one per scene type)
            → Blender headless (container)
                → PNG / EXR / MP4 / glTF
                       ↑
          egui live viewer ←┘
```

**Three layers:**

| Layer | Responsibility |
|---|---|
| Rust egui UI | Input, preview, validation, parameter rescaling, render trigger |
| scene_config.yaml | Validated contract — bpy script trusts this completely |
| bpy scene script | One hand-crafted Blender scene, parameterised from yaml |

---

##### Fluid strategy

Two distinct methods. Each semantically correct for its category.

| Method | What it is | When |
|---|---|---|
| Mantaflow Liquid | Physics fluid — splashes, pours, chaos | Category 1 art |
| Mantaflow Guide | Directed flow along a curve — no walls, no leaking | Category 2 & 3 info flow |

**Guide is the key insight for information flow.**
The curve *is* the pipe. No collision geometry → no leaking.
The pipe mesh is visual-only — glass tube, conduit, laser channel — zero physics role.
The fluid is mathematically steered along the graph edge path.
No walls exist, so nothing can leak through them.

This matches the semantics: information doesn't slosh and leak. It travels a path.
It has direction and intent. It follows the graph topology.

Speed and colour of each guided flow come directly from edge data —
high-traffic connections flow faster, weighted edges glow brighter.

---

##### Category 1 — Art scenes

**Fluid method:** Mantaflow Liquid (physics)  
**Inputs:** Scalars + enums + asset (logo / image)  
**Validator:** Geometry + quality constraints  
**Preview:** Parameter checklist + logo rescale handle  
**Output:** PNG / EXR / MP4

```ansi
      ╲  │  ╱      
       ╲ │ ╱       
   ─────[◈]─────     ← logo
       ╱ │ ╲       
      ╱  │  ╲      
   ≋≋≋≋≋≋≋≋≋≋≋≋≋     ← fluid
```

Scenes:
- Raytraced god rays (volumetric scatter)
- Metal reflections (anisotropic BSDF — gold / steel / chrome / copper)
- Water / liquid (glass BSDF + IOR)
- Fluid through pipes (Mantaflow Liquid)
- Volumetric clouds
- Logo integration — auto-normalised to scene units

**Build this first.** One hand-crafted Blender scene. Extract 5–8 parameters.

---

##### Category 2 — System topology

**Fluid method:** Mantaflow Guide (information flow)  
**Inputs:** Structural graph — what connects to what  
**Validator:** Graph structure validator  
**Preview:** egui_graph editor — confirm layout before render  
**Output:** PNG / MP4 OR glTF → egui live viewer

```ansi
   ●━━━━●━━━━━━━●    line A
        ┃          
   ●━━━━◉━━━●        ← interchange
        ┃          
        ●━━━━━━━●    line C
```

Each graph edge becomes a bezier curve. Guide flow attaches to the curve.
The pipe mesh is purely visual — glass tube around the curve, no physics.
Information visibly travels the dependency graph, directed and leak-free.

Input sources:
- Draw in egui_graph canvas
- Import JSON / YAML / Terraform / k8s / docker-compose
- Two-way: import → edit → export back to JSON

Graph validator:
- Circular deps → warn, flag in render
- Orphaned nodes → layout decision
- Node count > 50 → readability warning
- Missing labels → fallback to node ID

---

##### Category 3 — Data graphs (cinematic portrait)

**Fluid method:** Mantaflow Guide (information flow)  
**Inputs:** Property graph — nodes and edges carry real values  
**Validator:** Schema + value range validator  
**Preview:** 2D graph + value→visual mapping config  
**Output:** PNG / MP4 OR glTF → egui live viewer

```ansi
   ◉━━━━━━◎             ← small
   │╲              
   │  ◉               ← mid
   │    ╲          
   ◎━━━━━◉              ← large
```

The scene:
- Nodes → glass spheres / crystal polyhedra / liquid-metal orbs
- Edges → guided fluid in glass pipes / glowing laser beams / energy arcs
- God rays through the node cluster
- Volumetric fog — edges scatter visibly
- Depth of field — foreground sharp, background bokeh
- Node size + emission = property value
- Edge flow velocity / glow = relationship weight

Value → visual mapping (configured in egui):
- Which property → node size?
- Which property → emission brightness?
- Which property → edge flow velocity / laser intensity?
- All values normalised 0–1 before bpy

Input sources:
- FalkorDB — live Cypher queries
- Neo4j dump / GraphML
- Property graph JSON

Validator concerns:
- Glass caustics > 50 nodes → render time explodes → warn
- Edge count → guide volumes expensive → cap or batch

Live path: FalkorDB → poll → egui graph viewer live.
Blender = the beautiful artifact export.

---

##### Category 4 — Physical rack topology

**Inputs:** Server inventory — rack units, slots, hardware specs, cable routes  
**Validator:** Rack geometry validator (U-count, slot conflicts, power budget)  
**Preview:** egui rack diagram — side-on RU view, confirm layout before render  
**Output:** PNG / EXR / MP4 fly-through OR glTF → egui live viewer

```ansi
   ╔══════════════╗  
   ║ ▬▬  srvA  ● ║   ← cool
   ║ ▬▬  srvB  ● ║   ← normal
   ║ ▬▬▬ srvC  ● ║   ← hot
   ║ ░░  [empty]  ║  
   ╚══════════════╝  
```

The scene:
- Rack chassis as brushed steel enclosure — perforated front panels, cable management arms
- Servers as 1U / 2U / 4U sleds — indicator LEDs emissive, drive bays visible
- Switches → dark anodised aluminium, SFP cages glowing
- Cable runs → colour-coded fibre and copper bundles, volumetric glow on active links
- God rays through server room — dust motes, cold aisle / hot aisle separation visible
- Active ports → pulsing emission
- Load / temp values → heat maps on chassis surfaces

Value → visual mapping (configured in egui):
- CPU load → chassis surface heat map (cool blue → alert red)
- Network utilisation → cable glow intensity
- Power draw → PDU indicator brightness
- Disk activity → drive bay LED pulse rate

Validator concerns:
- U slot overlap → block render, flag conflict
- Power budget exceeded → warn per PDU rail
- Cable count > 100 → batch into trunks for render performance
- Missing U slots → show empty blanking panels

Input sources:
- Netbox REST API — live pull of rack inventory
- Static `rack.yaml` — version-controllable, import/export
- Manual egui editor — drag servers between U slots

---

##### Visual style reference

Network and topology views follow a schematic transit-map aesthetic —
lines as routes, nodes as stations, edges carrying weight through
thickness and glow intensity. Orthogonal routing, interchange dots,
color-coded layers.

```ansi
   ●━━━━━━━━━━━●━━━━━●    line A
               ┃       
   ●━━━━━━━━━━━◉━━━━━●    line B
               ┃       
               ●           ← interchange
```

In egui_graph: node size encodes property value, edge thickness encodes
relationship weight, color temperature encodes load or state.
The Blender export is the cinematic version of the same data.

---

##### Scene file format

All egui settings saveable and loadable as a **kyokkou scene file** —
the full UI state: category, scene type, all parameter values, graph layout,
value→visual mappings, validator state.

**RON** — Rust-idiomatic, enums serialize cleanly, comments allowed,
readable by power users. Scene files are not machine interchange — readability wins.

```ron
// kyokkou scene — art / fluid pipe
(
    category: ArtScene,
    scene_type: FluidPipe,
    metal: Chrome,
    render_samples: 512,
    output_type: Still,
    logo_path: Some("/assets/logo.png"),
    logo_scale: 0.42,
)
```

Scene files load back into egui state exactly. The bpy script still receives
the validated `scene_config.yaml` — the scene file is the egui side of the contract.

---

##### Render backend — bpy in a container

**Runtime: Podman.** Rust speaks the Podman socket directly via bollard
(`DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock`). No Docker needed.

bpy (Blender as a Python module, official PyPI package) runs inside a custom container.
No suitable off-the-shelf image exists. All custom images share the same Ubuntu base +
bpy install + Python HTTP agent. The compute layer is the only difference.

###### Two deployment scenarios

**Scenario A — outside** (default): Rust egui runs on the host, agent runs in the container.
HTTP on `localhost:8080` bridges them. Rust lifecycle-manages the container via bollard.

**Scenario B — bundled**: Rust egui + agent both inside the container. One `podman run`
ships everything. Entrypoint starts the Python agent then exec's the Rust binary.
Display forwarded via Wayland socket or X11 socket at runtime.

###### Images

| Image | Scenario | GPU | Device passthrough |
|---|---|---|---|
| `kyokkou-cpu` | A | CPU fallback | — |
| `kyokkou-rocm` | A | AMD ROCm/HIP | `/dev/kfd`, `/dev/dri` |
| `kyokkou-cuda` | A | NVIDIA CUDA | `--gpus all` |
| `kyokkou-bundled-cpu` | B | CPU fallback | — |
| `kyokkou-bundled-rocm` | B | AMD ROCm/HIP | `/dev/kfd`, `/dev/dri` |
| `kyokkou-bundled-cuda` | B | NVIDIA CUDA | `--gpus all` |

`kyokkou-cpu` / `kyokkou-bundled-cpu` are always the fallback.
ROCm 7.2.2 + HIP confirmed working on host (klient-t14s) — reference for the ROCm images.

**Target: Blender 5.1.1 · bpy 5.1**

**Hardware:** kyokkou is not tied to any specific GPU. Cycles runs on whatever
the host has — AMD ROCm, NVIDIA CUDA/OptiX, Apple Metal, or CPU fallback.
Current development machine is a ThinkPad T14s with Radeon 890M (ROCm),
but a 4090 or any CUDA card drops in without code changes — just pass the
right device through to the container.

bpy is a Python C extension that IS Blender internals — scene graph, materials,
node trees, render engine — loaded directly into a Python process. The only way
to drive it from Rust is via an HTTP agent in the same process as bpy.
The agent calls bpy functions directly in shared memory.

**What the container solves:**

- **CUDA conflict** — `import bpy` calls `cuInit()`. egui already holds a GPU context.
  Container gives bpy exclusive CUDA ownership, full GPU Cycles on the same card.
- **Host bleed** — Python version, numpy, system libs pinned inside. Reproducible renders.
- **Python pinning** — bpy 5.1 requires exactly Python 3.13. Container owns that.
- **GPU/EGL** — Cycles full GPU, EEVEE Next via EGL. Pass `--gpus all` via bollard.

**Volume mounts** set at container creation:
- `/tmp/kyokkou/in` → `/in` — yaml, textures, assets
- `/tmp/kyokkou/out` → `/out` — PNG, EXR, MP4 output

**HTTP agent** (stdlib only, no framework) inside the container:

| Endpoint | Behaviour |
|---|---|
| `GET /health` | 200 when bpy is initialised and ready |
| `POST /render` | Accepts yaml body, calls bpy, blocks until done, returns 200 |
| `POST /upload` | Receives texture bytes, writes to `/in/textures/`, calls `bpy.data.images.load()` |

**Rust owns the container lifecycle** via `bollard` — speaks the Podman socket directly,
no CLI dependency (`DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock`).

Dependencies: `bollard 0.17` · `tokio 1` (full) · `reqwest 0.12` (json)

**Startup:** inspect via bollard → running: proceed / stopped: start / missing: create then start.
Poll `GET /health` until 200, then unlock render button.

**Render:** validator green → Rust posts yaml to `POST /render` → tokio task awaits 200 →
reads from `/tmp/kyokkou/out` → displays in egui preview. egui thread never blocks.

---

##### The parameter validator

Every input has a type, range, and cross-parameter constraints.
egui shows live checkbox state — green / yellow / red.
**Render button locked until all checks pass.**

Examples:
- Logo scale re-validated when FOV changes
- Volume clouds → quality auto-upgraded to min 512 samples
- Animation mode → disables still-only params
- Glass node graph > 50 nodes → warn, offer simplified material
- FalkorDB values → normalised 0–1 before hitting bpy
- Logo → normalised to scene units, rescale handle in egui preview

The bpy script trusts the yaml. egui guarantees correctness.

---

##### egui_graph — first-class editor

- Draw topology from scratch
- Import JSON → auto-layout (force-directed / Sugiyama) → edit
- Export back to JSON — version-controllable
- Drives Blender scene OR live egui viewer directly

---

##### What bpy sees — after validation passes

```yaml
scene_type: clouds_metal_logo
metal: chrome
cloud_type: cumulus
logo_path: /assets/logo.png
logo_scale_scene_units: 0.42
logo_position: [0.0, 0.0, 1.2]
camera_focal_mm: 35
render_samples: 512
output_type: still
output_path: /renders/out.png
```

---

##### Live egui path (no Blender)

```
FalkorDB (Cypher) → poll → parse property graph
    → egui_graph live viewer → values drive visuals
        → "Export to Blender render" → fires bpy scene
```

egui = live Grafana-like viewer.
Blender = beautiful artifact on demand.

---

##### Build sequence

1. One hand-crafted bpy scene — beautiful, hardcoded
2. Extract 5–8 parameters → `scene_config.yaml`
3. CLI: `kyokkou render --scene fluid_pipe --config scene.yaml`
4. Rust egui UI — writes yaml, live validation
5. Validator logic — per parameter, cross-constraints
6. Add scene types one by one

---

*Start here: one beautiful fluid pipe scene. Hand-craft it. Make it stunning.
Extract the parameters. Everything else follows.*


## mikronova/modgunn

At https://gunnar.rs/mikronova/modgunn.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

#### Móðguðr

> The nordisk supply-chain **gatekeeper**: a leaf Rust crate that hands any artifact a
> CVE / license / provenance **verdict** (Pass / Warn / Block) — and, with its heavy SCIP
> engine, diffs the *behaviour* of your entire dependency world between two snapshots over
> a time-travelling Iceberg warehouse.

<p align="center"><em>Móðguðr guards Gjallarbrú, the bridge to Hel — she challenges everyone who tries to cross and demands to know who they are.</em></p>

##### Mashup — the verdict, plus what *changed*

A scanner hands you today's verdict; **modgunn** also remembers every advisory and
world-snapshot and tells you what *changed* — categories the scanners have no concept for
(vs Trivy · Snyk · Dependency-Track):

| capability | **modgunn** | Trivy | Snyk | Dependency-Track |
|---|:--:|:--:|:--:|:--:|
| **Reachability-gated** vulns — is the CVE actually called? (SCIP call-graph) | **✅** | ✗ | **✅** | ✗ |
| Whole-world **behaviour-delta** between snapshots — capability drift, blast-radius, tripwires | **✅** | **NA** | **NA** | **NA** |
| Advisories **+ world-snapshots in a time-travel + CDC Iceberg warehouse** | **✅** | **NA** | **NA** | **NA** |
| Embeddable **leaf crate** — drop the gate (`core::Verdict`) inside another Rust app | **✅** | ◐ | ✗ | ✗ |

*The full matrix — every capability, all rivals — is in the [overview](.nornir/README-full.md). Rows marked **NA**: the rival has no such category, not a missing feature.*

##### Measured — the verdict hot path

The numbers below are **generated** from the warehouse `bench_runs` table by
`nornir docs render modgunn`; they come from `nornir bench run modgunn`
(`bench/examples/nornir-bench.rs`) over a deterministic synthetic advisory world.
`fleet_scan` is the shipped Engine A (linear match) fleet path, `fleet_scan_indexed`
the Engine B (inverted-index) one over the *same* fixture — and the harness asserts
both find exactly the same findings, so the speedup is honest.

<!--
  `benches`, not `mashup`: modgunn benches no rival binary here (the rival comparison
  is the capability matrix above), so mashup's N-way head-to-head has nothing to
  discover — and its `_`-delimited system-token inference would fold
  `modgunn.fleet_scan` and `modgunn.fleet_scan_indexed` into ONE row, dropping exactly
  the A-vs-B comparison this table exists for.
-->
<!-- nornir:gen:start:benches winner=row -->
**v0.0.5 · oden · 32 cores · 2026-07-22**

| workload | advisories | blocked | cores | elapsed_ms | index_buckets | index_build_ms | n | ns_per_verdict | packages | telem_cores_busy_avg | telem_cores_busy_max | telem_cpu_pct_avg | telem_cpu_pct_max | telem_elapsed_ms | telem_mem_pct_max | telem_mem_peak_mb | telem_n_cores | total_findings | verdicts_per_sec |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| modgunn.fleet_scan | 4000 | | 32 | 35 | | | 20000 | 1757 | 600 | 28 | 28 | 59.71 | 59.71 | **122.82** | 5.75 | 21.23 | 32 | 30846 | 0.569M |
| modgunn.fleet_scan_indexed | 4000 | | 32 | 7 | 1952 | 0.75 | 20000 | 334 | 600 | 4 | 4 | 14.21 | 14.21 | 889.15 | 5.73 | 27.04 | 32 | 30846 | 2.995M |
| modgunn.verdict_assemble | 4000 | 8645 | | **4** | | | 20000 | 184 | 600 | 2 | 2 | 8.20 | 8.20 | 719.56 | 5.62 | 27.04 | 32 | 30846 | **5.438M** |
<!-- nornir:gen:end:benches -->

##### Read the real docs

- 📖 **[The complete manual — `docs/manual.pdf`](docs/manual.pdf)** — every subsystem chapter (both engines, the warehouse seam, exploit-intel gating, the behaviour-delta analyses).
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the detailed pitch, the full competitive matrix, what each engine does, and the quickstart.
- 📝 [`docs/book.md`](docs/book.md) — the manual, as Markdown. Search it with `nornir docs search modgunn "<query>"`.


## mikronova/openzl-sys-rs

At https://gunnar.rs/mikronova/openzl-sys-rs.

### README.md

#### openzl-sys-rs

Low-level Rust bindings to [OpenZL](https://github.com/facebook/openzl), linked
statically.

##### The C sources are vendored

Building this crate needs **a C compiler and nothing else**:

* no network access,
* no `curl`, no `tar`, no `git`, no `cmake` — `build.rs` spawns no process at
  all,
* no C++ toolchain (the `openzl` target is pure C; 0.2.0's `-lstdc++` was never
  needed).

OpenZL's C sources, plus the exact zstd 1.5.7 and lz4 1.10.0 releases OpenZL
pins, live under `openzl/`. `build.rs` compiles them with the `cc` crate,
reproducing the flags OpenZL's own CMake used. See
[`openzl/VENDOR.md`](openzl/VENDOR.md) for provenance, upstream tarball
hashes, licences and the exact build configuration.

Up to and including 0.2.0 this crate downloaded a pinned `facebook/openzl`
tarball with `curl` at build time and drove `cmake`, which in turn fetched zstd
and lz4. That made the crate unbuildable offline, in a sandbox, or anywhere a
build is not allowed to reach the network. 0.3.0 removes all of it. The
compiled library is unchanged: same OpenZL commit, same zstd, same lz4, same
compile flags, and compressed output is byte-for-byte identical to 0.2.0's.

##### Which OpenZL is inside

The wrapper's version is not OpenZL's version. From 0.4.0 the crate version
names the bundled release as build metadata: `0.4.0+openzl.0.3.0`.

| openzl-sys-rs | OpenZL | Writes frame format | Reads frame formats |
|---|---|---|---|
| 0.1.x, 0.2.x, 0.3.x | 0.2.1 (commit `ca28e4bc`) | 25 by default | 8..=25 |
| 0.4.x | 0.3.0 (tag `v0.3.0`) | 27 by default | 8..=27 |

OpenZL 0.3.0 writes format 27 unless told otherwise, and a 0.3.x-wrapper reader
cannot decode that. A writer whose output must stay readable by older readers
sets `ZL_CParam_formatVersion` to 25 explicitly. 0.4.0's bindings follow
OpenZL 0.3.0's API renames (`ZL_MaterializerDesc2` → `ZL_MaterializerDesc`,
`registerBruteForceSelectorGraph` → `buildBruteForceSelectorGraph`); see
[`openzl/VENDOR.md`](openzl/VENDOR.md).

##### Using a pre-built library instead

Set `OPENZL_SYS_LIB_DIR` to a directory containing `libopenzl.a`, `libzstd.a`
and `liblz4.a` to link those and skip compiling the vendored tree. The build
script says so on stderr, and fails loudly if any of the three is missing.

##### Licence

The crate is BSD-3-Clause. It redistributes vendored C sources under
BSD-3-Clause (OpenZL; zstd, whose BSD option is elected) and BSD-2-Clause
(lz4's `lib/`). Package licence expression: `BSD-3-Clause AND BSD-2-Clause`.


## mikronova/tyr

At https://gunnar.rs/mikronova/tyr.

### README.md

#### tyr

**Our own eID — locked down, mega small, binary, fast from embedded up.**

`tyr` (Old Norse **Týr**, god of law and *oaths* — the one who bound the wolf Fenrir with his own
hand as the pledge) is Nordisk's *own* electronic-identity protocol. Where [`frida`](../frida) buys
EU interoperability with heavyweight envelopes (JOSE/COSE, SD-JWT, mdoc), `tyr` makes the opposite
trade: a tight **binary** wire protocol for constrained hardware and very high throughput.

> Lesson behind it: an XML-era identity protocol, re-done as a binary protocol, cost **~100× less**
> CPU/space. `tyr` starts binary.

##### Goals

- **Compact binary wire format** — no XML/JSON; fixed/TLV framing, versioned, minimal allocations.
- **Embedded-friendly** — a `no_std`-capable core codec; runs on constrained devices.
- **Fast + scalable** — cheap to parse, cheap to serve; scales from a microcontroller to a fleet.
- **Locked down** — small attack surface, modern crypto only, hardware-bound keys.
- **auth + sign** — deliberately minimal capability set (no issuance/wallet sprawl).

##### Holders

- **Device key** — hardware-backed key in a mobile / embedded secure element.
- **Card reader** — smartcard over **PKCS#11**.

##### Who uses it

Vetra's **`heimdal`** integrates `tyr` as a provider (`heimdal-tyr`) behind its `IdentityProvider`
trait, alongside Freja, BankID, and `frida`.

##### Persistence & deployment — no relational database, ever

Same `Store` trait as `frida` (lives in `edda`, following `nornir-warehouse-trait`); never an RDBMS.

- **Single server (the common case for `tyr`)** — embedded **redb / rocksdb**; often *the whole
  deployment* on constrained hardware.
- **Multi-server** — scaled via **Kubernetes**; an **append-only event log with per-event ACK** is
  the source of truth, with **skade** (Iceberg lakehouse) as the sink for large transactional /
  audit volume.
- **Networking** — **WireGuard** node-to-node (natural via **Cilium transparent WireGuard**).
  Cilium guarantees the *network* (connectivity, policy, encryption), not application **ACK** —
  delivery/ACK is the event log's job.

##### Repo layout (planned)

```
spec/                the binary wire format + crypto suite (the product)
crates/
  tyr-proto/           no_std wire codec (frames, TLV, versioning)
  tyr-crypto/          signature/attestation suite (Ed25519 / ECDSA)
  tyr-server/          high-speed server
  tyr-client/          client (used by heimdal-tyr)
  tyr-embedded/        constrained-device profile
```

Crypto / credential primitives shared with **`frida`** are expected to live in **`edda/`** rather
than being duplicated.

##### Status

**Design.** No protocol code yet — see [`docs/PROTOCOL.md`](docs/PROTOCOL.md).

##### License

**0BSD** (Zero-Clause BSD) — maximally permissive, no attribution required. See [`LICENSE`](LICENSE).


## mikronova/workspace_znippy

At https://gunnar.rs/mikronova/workspace_znippy.

This repository has no README.

## mikronova/ya_egui

At https://gunnar.rs/mikronova/ya_egui.

### README.md



## nordisk/Skidbladnir

At https://gunnar.rs/nordisk/Skidbladnir.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<p align="center">
  <img src=".nornir/assets/skidbladnir.webp" alt="Skíðblaðnir — the ice-longship hauling the ZNIPPY cargo across the gap, shield-rabbits down the hull, under the aurora" width="440">
</p>

#### Skidbladnir

> **Skidbladnir** (Skíðblaðnir) is a 100% Rust airgap install framework: pack a whole
> deployment into one sealed, content-addressed bundle, carry it across the gap, and on the
> far side it creates the service, unpacks the znippy archive, and provisions the data & log
> dirs — starting every component in dependency order with no network and no shell on the target.

⚡ part of the **nordisk** constellation

##### Mashup — one sealed bundle onto a zero-network target

Skidbladnir carries the *whole* deployment — infra **and** software — over an airgap in
one content-addressed bundle and brings it up with **no agent, no SSH, no shell** on the
target (it drives systemd straight over D-Bus). The rivals each cover only a slice:

| capability | **Skidbladnir** | Ansible | Packer | cloud-init |
|---|:--:|:--:|:--:|:--:|
| One sealed, content-addressed bundle — infra **+** software + images + config + data, verify-on-receipt | **✅** | ✗ | ◐ (image only) | ✗ |
| **Zero network, zero shell** on the target — drives systemd over D-Bus, no SSH/agent | **✅** | ✗ (SSH + Python) | ✗ | ◐ (first-boot shell) |
| **Provision the machine** (Redfish BMC · Terraform · KVM) **and** install the software, one tool | **✅** | ◐ | ✗ | ✗ |

*The full seven-capability matrix — the `ServiceSpec` → PKI/systemd renderer tree, atomic version-swap + rollback, all-Rust offline — is in [the full overview](.nornir/README-full.md).*

##### Benchmarks

<!-- nornir:gen:start:bench_status -->
**Not benched.** This repo contributed no numbers to the latest run.

| | |
|---|---|
| `nornir-bench` entrypoint | **none** — nothing for the harness to run |
| criterion `[[bench]]` targets | **none** |
| host-stamped result | **none** |

There is no benchmark harness here at all — no `nornir-bench` entrypoint and no criterion `[[bench]]` target. Nothing was skipped and nothing failed: there is nothing to measure yet. Adding an `examples/nornir-bench.rs` puts this repo in the host-stamped tables.
<!-- nornir:gen:end:bench_status -->

<sub>This block is rendered, not written: the "no numbers" state is a **result**, probed
from this repo on every `nornir docs render Skidbladnir`. It is here so nobody has to
guess whether Skidbladnir was measured and came out slow, or was never measured at all —
an absent table would have read as the former.</sub>

##### Read the real docs

- 📖 **[The complete manual — `docs/manual.pdf`](docs/manual.pdf)** — every subsystem chapter (pack · archive · move · unfold, PKI, layout, the Redfish/Terraform/KVM backends).
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the detailed pitch, quickstart, the full capability matrix, packet format, and design.
- 📝 [`docs/book.md`](docs/book.md) — the manual, as Markdown.


## nordisk/ci-status

At https://gunnar.rs/nordisk/ci-status.

### README.md

#### CI status: ci

Written by the dwarves forge (`dwarves serve`, its `[ci]` table) at 2026-10-29 15:20 UTC. Each pipeline runs on a new head of its branch (polled, last poll 2026-10-29 15:13:37.584698594) or on its cron, one run at a time.

| pipeline | repo | branch | commit | trigger | result | finished (UTC) | took | files |
|---|---|---|---|---|---|---|---|---|
| gunnar-ci | gunnar |  |  |  | ⚪ not run yet |  |  |  |
| holger-ci | holger |  |  |  | ⚪ not run yet |  |  |  |
| gunnar-ui-ci | gunnar-ui |  |  |  | ⚪ not run yet |  |  |  |
| holger-ui-ci | holger-ui |  |  |  | ⚪ not run yet |  |  |  |
| monetize-ci | monetize |  |  |  | ⚪ not run yet |  |  |  |
| edda-ci | edda |  |  |  | ⚪ not run yet |  |  |  |
| scorecard-gunnar | gunnar | main | `` | manual | 🔴 red | 2026-10-29 15:18 | 0m57s | `runs/scorecard-gunnar/20261029T1518Z-nohead` |
| scorecard-holger | holger | main | `c1d6d1a1` | manual | 🟢 green | 2026-10-29 15:20 | 1m50s | `runs/scorecard-holger/20261029T1518Z-c1d6d1a1` |

##### Why red

- **scorecard-gunnar** ``: fetch gunnar@main: git fetch -q --no-tags origin +refs/heads/main:refs/remotes/origin/main in /home/rickard/tgt/ci/src/gunnar: fatal: couldn't find remote ref refs/heads/main

##### scorecard-holger

OpenSSF Scorecard v5.6.0, local mode, on `holger` at `unknown`: **2.9 / 10** (the aggregate of the checks local mode runs).

| check | score | reason |
|---|---|---|
| Binary-Artifacts | 6 / 10 | binaries present in source code |
| Dangerous-Workflow | inconclusive | no workflows found |
| Dependency-Update-Tool | 0 / 10 | no update tool detected |
| Fuzzing | 0 / 10 | project is not fuzzed |
| License | 9 / 10 | license file detected |
| Packaging | inconclusive | packaging workflow not detected |
| Pinned-Dependencies | inconclusive | no dependencies found |
| SAST | 0 / 10 | no SAST tool detected |
| Security-Policy | 10 / 10 | security policy file detected |
| Token-Permissions | inconclusive | No tokens found |
| Vulnerabilities | 0 / 10 | 10 existing vulnerabilities detected |

Not run in local mode (they need a GitHub or GitLab API): Branch-Protection, CI-Tests, CII-Best-Practices, Code-Review, Contributors, Maintained, SBOM, Signed-Releases, Webhooks.

##### History

| finished (UTC) | pipeline | commit | trigger | result |
|---|---|---|---|---|
| 2026-10-29 15:20 | scorecard-holger | `c1d6d1a1` | manual | 🟢 green |
| 2026-10-29 15:18 | scorecard-gunnar | `` | manual | 🔴 red |

The files of a run (the forge's step log, the repository's own `receipt.json` and step logs) are in this repository under `runs/<pipeline>/`; clone it with `git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/ci-status.git`.


## nordisk/dwarves

At https://gunnar.rs/nordisk/dwarves.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<!-- nornir:class: readme -->
<!--
  GOLD README PATTERN. This is the *source* — the repo-root README.md is GENERATED
  from `.nornir/README.md` by `nornir docs render` and carries a
  "⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file" banner.
  Edit here, never the root copy.
-->

![dwarves](assets/dwarves.webp)

#### Dwarves

CI/CD software forge inspired by the dwarven craftsmanship that created Odin's spear Gungnir and the gods' treasures, as recorded in the Icelandic Prose Edda (c. 1220 CE).

It binds tightly to **[Nornir](https://git.gunnar.rs/nordisk/nornir)** — handling code's time-space dimensional warehouse — and to **[Holger](https://git.gunnar.rs/nordisk/holger)**, the artifact registry guarding your artifacts at rest.

##### Measured — the forge's planner hot paths

<!--
  `benches`, not `mashup`: dwarves benches no RIVAL system, so there is nothing for
  mashup's N-way head-to-head to compare against — and mashup infers its "system"
  token from the leading `_`-delimited segment of a result name, which folds
  `dwarves.incremental_cold` and `dwarves.incremental_warm` into ONE row and silently
  drops the warm (cache-hit) arm. `benches winner=row` keeps every arm as its own row.
  Numbers come from `nornir bench run dwarves` (bench/examples/nornir-bench.rs).
-->
<!-- nornir:gen:start:benches winner=row -->
**v0.1.4 · oden · 32 cores · 2026-07-22**

| workload | build_ms | builds_per_sec | cache_hits_per_sec | elapsed_ms | ids_per_sec | iters | replan_ms | run_ms | steps | steps_per_sec | telem_cores_busy_avg | telem_cores_busy_max | telem_cpu_pct_avg | telem_cpu_pct_max | telem_elapsed_ms | telem_mem_pct_max | telem_mem_peak_mb | telem_n_cores |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dwarves.dag_build | 0.12 | 8546 | | 2340 | | 20000 | | | 150 | | 2 | 2 | 6.63 | 7.04 | 2342 | 5.44 | 16.98 | 32 |
| dwarves.incremental_cold | | | | **78** | | 200 | | 0.39 | 150 | 386271 | 2 | 2 | 6.25 | 6.25 | **79.07** | 5.44 | 17.55 | 32 |
| dwarves.incremental_warm | | | 520476 | 144 | | 500 | 0.29 | | 150 | | 2 | 2 | 6.44 | 6.44 | 145.91 | 5.45 | 17.55 | 32 |
| dwarves.step_input_id | | | | 361 | 1.38M | 500000 | | | | | 2 | 2 | 7.03 | 7.03 | 361.39 | 5.46 | 17.55 | 32 |
<!-- nornir:gen:end:benches -->

---

##### Dwarves release — usage

**Dwarves release** is a multi-language, industrialised implementation of cunning, the cargo/crate spec, and academia — built to solve the Codeberg/Nordisk release: **154 crates across 17 repos**, cross-repo cycles and all.

```sh
dwarves workspace-release .             # DEFAULT dry-run — PLAN ONLY: plan the constellation, no build, no branch
dwarves workspace-release . --verify    # + the pre-publish cargo-test suite
dwarves workspace-release . --rehearse  # + Gate-B: rehearse the cascade into a throwaway local registry
dwarves workspace-release . --execute   # for real — verify + rehearse, then crates.io (only after both gates pass)
```

The run is **tiered** and **cheap-first, build-last**: the default dry-run **plans only** (deps-first order, ship-versions, the static Gate-A precheck, advisory graph analysis) and STOPS — no build, no `cargo test`, no branch — in **milliseconds**. `--verify` adds the cargo-test suite; `--rehearse` adds the Gate-B `/sparring` rehearsal; `--execute` implies **both** (a real publish always verifies + rehearses), then uploads. Nothing reaches crates.io until **two gates go green** — *Gate A* (static) and *Gate B* (a rehearsal into a throwaway local registry). A build never runs on an already-failed run, and a `--verify`/`--rehearse` dry-run builds into the **same target `--execute` reuses**, warming its cache.

In the Dwarves UI, the picture below shows a dry-run error — caught by the static Gate A in **milliseconds**, before any build. With `--rehearse`/`--execute`, Dwarves also **builds** the whole cascade into a **staged Holger** (a local repo):

<p align="center"><img src="assets/codevault-red.webp" alt="dwarves codevault — dry-run error: holger-server-lib red, missing the online feature" width="760"></p>

Fix the culprit and run again — green means the cascade publishes **deps-first**:

<p align="center"><img src="assets/codevault-green.webp" alt="dwarves codevault — both gates green, publishing deps-first" width="760"></p>

###### Several repositories in one run, and the live dry-run

`dwarves release` takes several repository paths and runs them in the order given.
`--only` spans all of them. A name that no path publishes, or that two paths publish,
is refused. `monetize-impl` is never published: a run that names it, or finds it, is
refused before anything is packaged.

```sh
# from the estate's parent directory: read-only, uploads nothing
dwarves release tunnr edda monetize Skidbladnir --only korp-installer,appliance-grow --live-dry-run
# the same line with --execute publishes (byte-identical crates are skipped, a 429 waits and retries)
```

`--live-dry-run` asks crates.io what is already published, runs the size gate, and
runs `cargo publish --dry-run` for each crate that would go up, then runs Gate B (the
loopback `/sparring` rehearsal) exactly as `--execute` would.

###### The pre-flight size gate

`dwarves release <repo>` weighs every crate it is about to upload **before** it uploads
anything, because **crates.io refuses an oversized upload with a bare HTTP 503** that
names neither a size nor a reason — cargo prints `failed to get a 200 OK response, got
503` and nothing else.

```sh
dwarves release .                       # dry-run: prints the registry's limit and where the number came from
dwarves release . --check-size          # + packages every crate NOW and weighs the .crate
dwarves release . --execute             # the size pass runs automatically, before any upload
dwarves release . --max-upload-mib 20   # a granted limit increase (or another registry's limit)
```

An over-limit crate **refuses the whole cascade** — exactly like the same-version drift
halt — naming the crate, its compressed size, the limit *and its source*, and the
**biggest directories and files in the `.crate`**, so the `exclude` to write is on
screen instead of behind a local `cargo package`. The two honest remedies are printed
with it: ship less, or get the limit raised and pass `--max-upload-mib`.

The limit is never a magic number. crates.io's **10 MiB** is a documented constant (no
registry endpoint publishes it, and a staff-granted per-crate increase is invisible to
the API); it is overridden by `--max-upload-mib`, `DWARVES_MAX_UPLOAD_BYTES_<ALIAS>` or
`DWARVES_MAX_UPLOAD_BYTES`, and for a non-crates.io `--registry` with no override the
limit is honestly **unknown** and refuses nothing. If a 503 still arrives mid-cascade,
it is translated: over the limit ⇒ "this 503 IS the size refusal", within it ⇒ "reads
as transient — retrying", never measured ⇒ it says so.

📖 **[Release pseudocode (Rust)](dry-run.md)** — the dry-run step by step, the two gates, and the formal model, with diagrams.

📐 **[Release invariants](release-invariants.md)** — what a release *guarantees*, independent of how it is sequenced: the cross-repo dependency model, the invariants upheld today, the ones measured as absent, and why a gate can state the right condition and still be green by construction.

*The release process is **AI-free** — 100% dwarven-forged: deterministic Rust, no model in the loop.*

---

Every crate in the constellation is held to **[Brokkr's Software Criteria](brokkr-software-criteria.md)** —
baremetal, minimal-dependency, zero-copy / multi-core / SIMD.


## nordisk/edda

At https://gunnar.rs/nordisk/edda.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

#### edda

> The nordisk **shared-lib samling** — the collected corpus (Norse *Edda*) of
> reusable, NON-facett library crates extracted from nornir so they release on
> their own cadence and are shared by nornir, korp, holger, skade, znippy, …

edda is a library repo, not an app. facett stays its own repo (it is the egui
component framework); edda is everything else that is a library. Each crate is a
lean leaf with a tiny dep set, so a consumer pulls only what it needs and no crate
drags nornir's warehouse / embedder / server tree behind it.

<!-- Write any intro prose here. Anything outside the marker blocks below
     is preserved verbatim on every render. -->

##### The crates

| crate | what it is |
|-------|-----------|
| **autonom** | the completeness / anti-drift subsystem — the constellation home for *coverage* (wiring-surface gate + test-matrix), and the runnable `autonom coverage <repo>` feeder that emits the code-that-lacks-tests worklist |
| **jera** | the shared infra runtime: durable **job ledger** (redb), a **no-shell container** runtime (bollard REST, delegated to draupnir's OCI backend), and **KVM boot** via draupnir. No `Command` shell-out on the tracked path |
| **nornir-testmatrix** | the portable core of the multi-aspect **test matrix** — runner (nextest/cargo), the pure discover/coverage engines, the UI-plane model. Tiny dep set so any leaf repo can run the same matrix |
| **nornir-robotui** | a reusable **robot-UI testing framework** for egui apps: drive real interactions (egui_kittest/AccessKit), observe via `state_json`, replay recorded scenarios, optional wgpu PNG snapshots |
| **nornir-saga** | capture a user's navigation as toolkit-neutral events → warehouse → **replay the path as a generated robot test**. The production sibling of nornir-testmatrix. `saga_to_ui(events)` is the event-model → UI-movement bridge (the inverse of `to_saga_events`) that folds a stored `SagaEvent` stream back into a replayable `Saga` |
| **nornir-bench** | the project-agnostic **bench harness**: a uniform run/result envelope + append-only JSONL history; repo metrics live in a free-form JSON object |
| **nornir-coverage** | source-based **line/region coverage** (cargo-llvm-cov), the JaCoCo-style report that answers "what fraction of source lines did the run execute?" |
| **nornir-git** | pure-Rust git helpers over **gix** (gitoxide) — inspection + history rewriting with no `git` shell-out and no libgit2/C |
| **nornir-cli-outcome** | the uniform CLI command result `{command, ok, data, human}` — the CLI twin of a Facet's `state_json` |
| **nornir-hash** | the shared **content-hash** primitive — lowercase-hex SHA-256 of bytes/files/streams (`sha256_hex`, `sha256_file`, `Sha256Hasher`) + a dependency-free `hex_encode`/`hex_decode` round-trip. `sha2`-only; consumed by every content-addressed store instead of re-rolling `Sha256::new();hex(...)` |
| **nornir-xtask-support** | the std-only **xtask primitives** toolbox (`publish=false`) four repos' `cargo xtask` crates hand-rolled identically: `which`/`run`/`copy_dir`/`target_dir` (`$CARGO_TARGET_DIR`-aware)/`short_rev` + the wasm bootstrap trio (`ensure_wasm_target`/`ensure_wasm_bindgen`/`locked_wasm_bindgen_version`, the blank-canvas-mismatch fix). Boxed `Send+Sync` error so every caller style adapts |
| **nornir-pages** | the parameterized **gh-pages demo/deploy flow** (`publish=false`) — `demo_wasm` (release wasm → wasm-bindgen → cache-busted static site into `dist/`) + `deploy_pages` (orphan-worktree force-push to the `pages` branch). All per-repo divergence is `PagesConfig`'s 11 knobs; builds on nornir-xtask-support |
| **nornir-humanize** | the shared **human-readable formatter** (`publish=false`, std-only, zero runtime deps) eight+ `human_bytes`/`human_count`/`fmt_elapsed` twins across nornir + facett drifted on: byte sizes (`bytes`→`1.5 KiB` IEC, `bytes_si`→`1.5 KB`), compact counts (`count`→`3.4M`), grouped digits (`thousands`→`1,234,567`), durations (`duration`/`duration_ms`/`duration_secs`→`2m 03s`), binary throughput (`throughput`→`3.5 MiB/s`) and ratios (`percent`/`percent_of`→`12.3%`). Canon = IEC-binary majority; SI dissenters documented |
| **nornir-escape** | the shared **markup-escaping** primitive (`publish=false`, std-only, zero runtime deps) — `xml(&str) -> Cow<str>` escapes the four XML/SVG entities (`&`→`&amp;`, `<`→`&lt;`, `>`→`&gt;`, `"`→`&quot;`), borrowing with no allocation when the input is clean. Seven byte-identical `xml_escape`/`svg_escape` copies across nornir/edda/Skidbladnir collapse into it; every repoint is byte-safe (apostrophe deliberately left unescaped, as every twin does). `xml_attr(&str)` is the **five-entity** sibling that ALSO escapes `'`→`&apos;` — for a single-quoted attribute value, where a bare apostrophe is not legal; byte-identical to `xml` on apostrophe-free input. Domain-coupled typst/markdown/slug escapers stay in their conductor |
| **nornir-appshell** | the two PURE, **egui-free** app-shell MODELS (`publish=false`, serde + serde_json only) every suite app's *About* + *Tools* menu hand-rolled: the **`Identity`** card (name/display_name/version/tagline/subtitle/`logo_png: Option<Vec<u8>>` LFS-tolerant/logo_alt/repository/links/framework/engines) + `state_json(open)` oracle, and the generic **`ToolMenu`**/**`ToolEntry`** (title/hover/effect_key/view_target/needs_container + `keys`/`find`/`state_json(last_picked)`). Lifts modgunn's `struct Identity`; korp/holger const-blobs + korp↔modgunn Tools shell collapse onto it. Each app KEEPS its `ToolEffect` enum + demo/container specs; the egui About *widget* is a later `facett-about` leaf |

###### The auth provider family

The **modern-auth** stack: each method is its OWN feature-gated leaf implementing
the shared `nornir_service::AuthProvider` seam, so a server links only the
providers its build pinned (an embedded/mini tier ships one bearer provider and
nothing heavier). Consumed by holger (reference impl), korp, dwarves.

| crate | auth method |
|-------|-------------|
| **nornir-auth-oidc** | OIDC / OAuth2 — validate a bearer JWT with the relying party's checks (algorithm allow-list, key fit, `iss` exact, `aud` required, `exp`/`nbf`) against a pre-provisioned JWKS (airgap) or the issuer's discovered `jwks_uri` (online); a JWT that does not verify is refused by name, never sent to `/userinfo`; an opaque token only through the opt-in, audience-bound introspection; plus the device-authorization-grant (RFC 8628) flow for interactive CLI login. One protocol covers Entra ID / Okta / Auth0 / Google / Keycloak / Zitadel |
| **nornir-auth-bearer** | static Bearer token / API-key — the tiny machine-to-machine method (CI runners, agents, headless clients); reuses the shared bearer-secret resolution ladder + constant-time compare |
| **nornir-auth-sshsig** | per-call SSH-signature credentials for a gRPC control plane, product-neutral: method-bound, single-use, time-boxed; one verifier + replay guard; each product brings its own metadata key and sshsig namespace (gunnar's wire format, now shared with monetize) |
| **nornir-auth-oauth2** | OAuth 2.0 / 2.1 — verify an access token via RFC 7662 introspection (SP side) + drive the client-credentials + authorization-code + PKCE flows. REUSES the `oauth2` crate; HTTP egress behind the `online` feature |
| **nornir-auth-apikey** | hashed static API-key — verify a presented key against SHA-256-HASHED keys at rest (rotating / multi-tenant stores); REUSES `nornir-hash::sha256_hex` + `nornir_service::token_matches`, no crypto/HTTP of its own |
| **nornir-auth-mtls** | mTLS — map a mutual-TLS leaf-cert Common Name (ALREADY extracted by the rustls transport) to an identity via `verify_client_cert`; consumes the CN, does NOT itself path-validate the CA |
| **nornir-auth-saml** | SAML 2.0 (enterprise SSO) Service Provider in pure Rust (`sp` feature): XML-DSig verification of a browser-posted Response (HTTP-POST binding) against the configured IdP certificate only, XSW-proof; issuer, audience, Destination, Recipient, InResponseTo, time window (skew at most 300 s); the AuthnRequest and both metadata documents. No samael, no C |
| **nornir-auth-webauthn** | WebAuthn / FIDO2 / passkey (passwordless human login), pure Rust (p256, no C) — `pure` (default): RP-side assertion checks (signature, challenge, origin, UP/UV flags, counter) and the MAC'd session token; `ceremony`: the server-held per-client challenge store and the assertion/attestation checks. The webauthn-rs path is deleted (A34-32) |
| **nornir-auth-client** | the shared CLIENT-side login — the missing `<app> login`: OIDC device-grant login (reusing nornir-auth-oidc's device helper), token acquire + silent refresh, and a `TokenStore` seam (default 0600 file store; pluggable to the dwarves Vault) |

nornir re-exports these (`nornir::bench`, `nornir::cli_outcome`, …) so its existing
call sites resolve unchanged after the extraction. Leaf repos consume
`nornir-testmatrix` + `nornir-robotui` from the crates.io registry by version.

##### Benchmarks

One `nornir bench run edda` covers every leaf that carries a bencher: `autonom`'s
source-scan fold, `nornir-flight`'s Arrow-Flight put/get round-trip, and
`nornir-saga`'s replay fold. The arms live inside the leaves (each behind that
leaf's off-by-default `bench` feature); `bench/` is the single repo-level
entrypoint that links them all — see `bench/examples/nornir-bench.rs`.

<!-- nornir:gen:start:benches winner=row -->
**v0.1.4 · oden · 32 cores · 2026-07-22**

| workload | batches | fns_per_sec | folds_per_sec | functions | gap | get_mib_per_sec | get_rows_per_sec | mib | movements | put_mib_per_sec | put_rows_per_sec | reps | rows | telem_cores_busy_avg | telem_cores_busy_max | telem_cpu_pct_avg | telem_cpu_pct_max | telem_elapsed_ms | telem_mem_pct_max | telem_mem_peak_mb | telem_n_cores |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| autonom.function_scan | | 151268 | | 600 | 200 | | | | | | | 30 | | 2 | 2 | 10.21 | 10.21 | **119.05** | 4.80 | 9.55 | 32 |
| nornir-flight.put_get_throughput | 20 | | | | | 1452 | 67.31M | 21.57 | | 762.58 | 35.36M | | 1.00M | 2 | 2 | 6.72 | 6.72 | 176.95 | 4.80 | 89.92 | 32 |
| saga-replay-fold | | | 149557 | | | | | | 2200 | | | 50 | | 2 | 2 | 6.45 | 6.45 | 735.54 | 4.80 | 89.92 | 32 |
<!-- nornir:gen:end:benches -->

##### Tests

<!-- Unit / integration / doc tests, enumerated from source via syn. -->
<!-- nornir:gen:start:tests -->
**Unit tests** (1113)

| file | tests |
|---|---|
| `crates/app-heartbeat/src/lib.rs` | `the_app_alive_line_round_trips_through_serial_text`, `first_app_alive_is_deploy_ok_and_beats_are_counted`, `a_generic_non_korp_app_drives_one_verdict_per_declared_function`, `no_app_alive_line_means_down_and_no_function_verdicts`, `deployed_but_beats_stopped_reads_degraded`, `a_failure_marker_sinks_a_function_even_with_the_expected_marker_present`, `korp_is_just_a_reference_manifest_the_generic_probe_parses` |
| `crates/autonom/src/allowlist.rs` | `absent_file_is_empty_allowlist`, `path_is_under_dot_nornir`, `save_then_load_round_trips` |
| `crates/autonom/src/bench.rs` | `scan_fold_bencher_runs_and_reports_throughput`, `generated_source_parses_and_has_a_gap` |
| `crates/autonom/src/feeder.rs` | `scans_fns_and_flags_test_roots`, `reachability_finds_the_orphan_as_needing_a_test`, `method_calls_resolve_and_cover`, `unparseable_source_is_skipped_not_fatal`, `integration_test_file_marks_all_fns_as_roots`, `module_prefix_reframes_crate_and_flags_tests` |
| `crates/autonom/src/report.rs` | `report_lists_only_unreached_functions`, `allowlist_excuses_a_function_from_the_list`, `markdown_is_deterministic_and_grouped`, `all_covered_reports_clean`, `groups_split_by_crate_head` |
| `crates/autonom/src/sentinel.rs` | `clean_blob_has_no_hit`, `case_insensitive_match_with_context`, `multibyte_haystack_does_not_panic` |
| `crates/eid-app/src/lib.rs` | `signs_and_verifies` |
| `crates/eid-core/src/lib.rs` | `challenge_distinguishes_auth_from_sign`, `assertion_debug_redacts_bytes` |
| `crates/eid-core/src/verify.rs` | `ed25519_round_trip_and_tamper`, `ed25519_weak_key_forgery_is_rejected`, `ecdsa_p256_round_trip` |
| `crates/jera/src/agent.rs` | `scheduler_matches_group_and_caps_then_runs_to_done`, `no_eligible_agent_returns_none_never_a_silent_drop`, `kill_handle_terminates_work_as_killed`, `agent_going_offline_kills_inflight_work`, `busy_agent_is_not_rescheduled_until_idle`, `grpc_agent_is_offline_skeleton_never_scheduled`, `grpc_agent_over_loopback_transport_runs_to_done`, `grpc_agent_over_loopback_transport_kill_propagates`, `work_payload_round_trips_through_json`, `payload_less_unit_still_decodes_and_legacy_path_unchanged`, `payload_carrying_unit_dispatches_and_reaches_the_runner`, `resource_req_round_trips_and_heavy_floors_the_cores`, `heavy_resource_req_never_places_on_a_nuc_class_node`, `exclusive_group_units_do_not_run_concurrently_across_the_fleet`, `non_exclusive_unit_is_never_gated`, `dispatched_units_result_round_trips_back_and_records_against_its_node`, `state_json_exposes_the_fleet_as_data` |
| `crates/jera/src/agent/roster.rs` | `stale_node_is_dead_after_the_window_and_heartbeat_revives_it`, `evict_dead_removes_only_the_stale_nodes`, `roster_saves_and_loads_durably`, `state_json_exposes_live_and_dead_as_data` |
| `crates/jera/src/agent_grpc.rs` | `grpc_dispatch_runs_to_done_over_the_wire`, `grpc_dispatch_carries_payload_over_the_wire`, `wire_auth_rejects_unauthenticated_dispatch_and_admits_the_authed_one`, `grpc_kill_propagates_to_killed_over_the_wire` |
| `crates/jera/src/boot.rs` | `mode_selection_maps_target_type_to_mode`, `press_boot_creates_a_running_job`, `poll_tracks_running_then_death_terminates_job`, `killing_the_job_kills_the_boot_and_marks_killed`, `spawn_failure_opens_and_immediately_fails_the_job`, `boot_jobs_with_reports_wires_reporter_and_state_json_carries_reports`, `boot_jobs_without_reports_state_json_has_no_reports_key`, `container_boot_through_manager_uses_boot_container_kind`, `vm_boot_through_manager_uses_boot_vm_kind_and_terminates`, `real_exe_boot_spawns_and_reaches_terminal` |
| `crates/jera/src/container.rs` | `container_spec_maps_onto_a_draupnir_container_spec`, `zone_offset_port_map_threads_distinct_host_container_onto_draupnir`, `resources_and_zone_reach_draupnir_and_default_is_all_cores`, `net_mode_renders_network_arg_and_default_is_byte_identical`, `container_net_reaches_draupnir`, `bind_mounts_render_bind_strings_and_empty_is_unchanged`, `draupnir_runner_boots_through_draupnir_streams_logs_and_kills`, `draupnir_runner_maps_container_state_to_terminal_status`, `draupnir_runner_boot_failure_is_reported`, `draupnir_runner_emits_health_starting_alive_then_gone`, `proc_exec_forwards_machine_and_argv_to_draupnir_and_returns_outcome`, `default_boot_proc_exec_is_honest_not_wired`, `stub_container_runner_reports_honest_not_wired`, `run_container_routes_through_draupnir_and_records_a_done_row`, `run_container_nonzero_exit_is_ok_but_records_a_failed_row`, `submit_container_job_records_knobs_and_waits_until_running`, `run_container_boot_failure_is_err_and_records_a_failed_row` |
| `crates/jera/src/jobs.rs` | `start_finish_fail_and_drop_zombie_guard`, `running_then_done_overwrites_in_place_no_duplicate`, `prune_drops_aged_terminal_and_caps_count_but_keeps_running`, `classify_bench_exclusive_others_parallel`, `daemon_job_reports_phase_and_stops_without_ever_failing`, `daemon_resumes_the_same_ledger_row_across_a_restart`, `daemon_phase_is_durable_across_a_fresh_read_only_store`, `start_or_resume_daemon_dedups_multiple_leftover_rows`, `exclusive_bench_jobs_serialize_second_observes_queued`, `parallel_job_not_blocked_by_running_exclusive`, `job_record_vec_json_round_trips`, `child_jobs_carry_parent_id_and_persist`, `job_records_its_node_and_persists_it`, `job_emits_progress_and_ledger_snapshot_reflects_current_and_total`, `workspace_sink_writes_a_job_into_the_env_resolved_ledger`, `workspace_sink_noops_on_unwritable_and_blank_env_falls_through` |
| `crates/jera/src/jobs/report.rs` | `report_key_gives_gauges_independent_slots`, `reporter_progress_lands_in_registry_latest_wins`, `reporter_health_transitions_are_recorded_in_order`, `reports_snapshot_shape_round_trips_through_serde`, `progress_fraction_and_meta_bridge_for_a_ui_row`, `reporter_phase_and_progress_coexist_latest_phase_wins`, `noop_reporter_never_panics_and_touches_nothing` |
| `crates/jera/src/policy.rs` | `policy_round_trips_with_boot_mode`, `vm_and_container_delegate_to_draupnir_process_does_not`, `draupnir_backend_maps_vm_to_kvm_and_container_to_oci`, `labels_are_stable` |
| `crates/jera/src/vm.rs` | `stub_vm_runner_reports_honest_not_wired`, `bootspec_kernel_rootfs_defaults`, `maps_onto_a_draupnir_kvm_spec` |
| `crates/korp-installer/src/banner.rs` | `banner_carries_the_korp_wordmark_and_raven_flavour`, `plain_banner_has_no_escape_codes`, `framebuffer_logo_paints_a_rich_branded_mark`, `framebuffer_logo_refuses_a_too_small_surface`, `rgbx_and_bgrx_place_the_red_channel_correctly` |
| `crates/korp-installer/src/bootorder.rs` | `hd_node_is_a_spec_shaped_gpt_harddrive_path`, `load_option_decodes_back_to_its_description_and_device_path`, `writing_a_boot_entry_puts_it_first_in_bootorder`, `re_running_does_not_duplicate_the_entry_in_bootorder`, `efivars_writable_is_false_when_absent` |
| `crates/korp-installer/src/cpu.rs` | `detects_amd_and_selects_amd_ucode_not_intel`, `detects_intel_and_selects_intel_ucode`, `an_unknown_vendor_still_installs_and_is_reported_honestly`, `a_cpuinfo_with_no_vendor_line_is_other_not_a_guessed_intel`, `a_missing_cpuinfo_file_is_not_fatal` |
| `crates/korp-installer/src/esp.rs` | `formats_a_fat32_esp_and_places_the_boot_payload`, `small_esp_falls_back_to_a_valid_fat_and_still_holds_files`, `rejects_a_partition_too_small_for_fat` |
| `crates/korp-installer/src/firststage.rs` | `the_esp_contract_matches_what_the_esp_builder_lays_down`, `recognises_a_uefi_pe_and_refuses_anything_else`, `the_first_stage_source_is_in_tree` |
| `crates/korp-installer/src/gpt.rs` | `writes_a_valid_two_partition_gpt`, `writes_a_third_korp_data_partition_when_requested`, `no_data_partition_is_byte_identical_to_the_two_partition_table`, `a_data_partition_too_big_for_the_rootfs_is_a_hard_error`, `backup_header_sits_at_the_last_lba_and_points_back`, `deterministic_for_a_fixed_seed`, `rejects_a_disk_too_small`, `rejects_an_esp_that_does_not_fit` |
| `crates/korp-installer/src/gpu.rs` | `nvidia_gets_nouveau_in_image_and_flags_proprietary_recommended`, `amd_and_intel_get_their_in_tree_drivers_with_no_flag`, `no_gpu_is_headless_and_never_blocks_the_install`, `an_unknown_adapter_falls_back_to_the_firmware_framebuffer`, `multiple_gpus_are_each_recorded` |
| `crates/korp-installer/src/hwdetect.rs` | `picks_the_nvme_over_sata_and_skips_loop_and_removable`, `an_empty_pin_is_auto_select`, `a_name_pin_selects_that_disk_not_the_auto_favorite`, `a_pin_that_matches_nothing_fails_closed_and_never_wipes_the_wrong_drive`, `a_size_pin_disambiguates_two_same_class_disks`, `a_wwn_pin_matches_by_stable_identity`, `reads_the_disk_model_from_sysfs_and_targets_the_nvme_node`, `detects_uefi_and_gpu_and_renders_the_finding_line`, `no_gpu_renders_none_and_bios_is_rejected`, `no_disk_is_an_honest_error`, `drm_connectors_and_render_nodes_are_not_counted_as_gpus` |
| `crates/korp-installer/src/install.rs` | `full_install_lays_gpt_esp_and_rootfs_and_reaches_install_ok`, `hardware_survey_records_land_on_the_esp`, `a_failed_step_prints_a_loud_failed_line_and_errors` |
| `crates/korp-installer/src/loader.rs` | `blake3_verify_accepts_the_pinned_image_and_refuses_a_flipped_byte`, `parse_blake3_hex_round_trips`, `exec_from_memory_runs_a_ram_only_binary` |
| `crates/korp-installer/src/mdraid.rs` | `superblock_carries_the_magic_level_and_bootable_data_offset`, `super_offset_is_8kib_from_the_end_and_4kib_aligned`, `the_checksum_is_self_consistent_and_covers_the_roles`, `two_members_share_the_array_uuid_but_differ_in_dev_number` |
| `crates/korp-installer/src/payload.rs` | `streams_payload_raw_onto_the_partition_offset`, `refuses_a_payload_larger_than_the_partition`, `embedded_source_opens_and_s3_is_an_honest_seam`, `human_bytes_render` |
| `crates/korp-installer/src/raid.rs` | `two_equal_ssds_are_paired_and_a_lone_disk_is_not`, `disks_without_a_model_are_never_auto_paired_on_size_alone`, `policy_off_never_mirrors_and_require_fails_without_a_pair`, `mdadm_examine_recognises_the_member_as_a_raid1_mirror`, `a_raid1_install_lays_identical_bootable_members_with_superblocks` |
| `crates/korp-installer/src/secureboot.rs` | `the_authenticode_digest_skips_the_checksum_and_cert_directory_only`, `a_signed_image_still_hashes_to_the_unsigned_digest`, `enrolling_a_certificate_writes_pk_kek_and_db`, `the_uefi_guids_are_byte_exact`, `refuses_to_sign_something_that_is_not_a_pe` |
| `crates/korp-installer/src/selftest.rs` | `a_good_payload_reports_every_leg_it_actually_ran`, `a_byte_flipped_on_the_installed_disk_fails_the_install_selftest`, `a_tampered_signature_fails_only_the_signature_leg`, `an_unsigned_payload_is_refused_and_the_verdict_names_the_signature_leg`, `an_unenrolled_anchor_is_reported_honestly_and_never_as_a_pass`, `a_small_order_enrolled_pubkey_fails_rather_than_verifying_everything`, `a_failed_earlier_leg_stops_the_startup_chain_before_extraction`, `the_verdict_renders_a_parseable_line_and_its_reasons` |
| `crates/korp-installer/src/serial.rs` | `every_line_carries_the_installer_tag_and_crlf`, `failed_line_is_loud_and_structured`, `install_ok_emits_the_exact_gate_marker`, `try_line_surfaces_a_write_error` |
| `crates/korp-installer/src/trust.rs` | `default_verifier_trusts_and_default_key_source_is_plaintext`, `ed25519_root_verifier_accepts_a_valid_signature_and_refuses_the_rest`, `a_custom_verifier_can_refuse` |
| `crates/nornir-appshell/src/lib.rs` | `identity_state_json_carries_name_version_framework_and_logo`, `identity_round_trips_through_serde`, `logo_option_handled_none_and_lfs_pointer`, `toolmenu_enumerates_and_resolves_selected_entry`, `toolmenu_round_trips_through_serde`, `emits_honest_rows_for_appshell_surfaces` |
| `crates/nornir-auth-apikey/src/lib.rs` | `matching_hashed_key_authenticates_wrong_is_not_mine`, `store_holds_hash_not_plaintext`, `multi_key_store_and_empty_store_fail_closed` |
| `crates/nornir-auth-bearer/src/lib.rs` | `matching_key_authenticates_wrong_is_not_mine`, `empty_secret_provider_is_disabled_fail_closed`, `resolves_secret_via_shared_ladder_env_and_file` |
| `crates/nornir-auth-client/src/lib.rs` | `bundle_validity_honours_expiry_and_skew`, `from_response_stamps_expiry_and_preserves_refresh`, `cached_token_returns_fresh_hides_expired`, `file_store_round_trips_and_is_owner_only`, `for_app_path_convention`, `login_against_the_stub_yields_and_persists_a_token`, `no_refresh_token_and_a_stale_access_token_means_log_in_again`, `display_jwt_carries_its_claims_in_a_decodable_payload` |
| `crates/nornir-auth-mtls/src/lib.rs` | `cn_becomes_identity_bearer_is_not_mine`, `principal_for_cn_handles_absent_cn`, `ca_trust_warning_only_when_ca_declared` |
| `crates/nornir-auth-oauth2/src/lib.rs` | `active_token_with_subject_verifies`, `active_token_falls_back_to_username`, `inactive_token_is_rejected_as_not_mine`, `active_but_subjectless_token_is_rejected`, `disabled_provider_authenticates_nothing`, `authorize_url_pkce_builds_a_valid_redirect` |
| `crates/nornir-auth-oidc/src/lib.rs` | `valid_jwt_verifies_offline_against_inline_jwks`, `jwt_scopes_are_surfaced`, `bad_jwts_are_authoritatively_rejected`, `fully_offline_preprovisioned_jwks_file_succeeds`, `opaque_token_is_not_mine_when_offline`, `device_code_parse_and_user_uri`, `token_response_parse_success_pending_and_error`, `a34_1_the_key_set_comes_from_discovery_not_a_guess`, `a34_1_a_cold_jwks_cache_refuses_and_never_falls_through`, `a34_29_an_oct_key_never_verifies_an_hmac_bearer`, `jwks_fetched_once_then_cached` |
| `crates/nornir-bench/src/api.rs` | `an_empty_filter_value_means_no_filter_not_match_nothing`, `a_preview_stamp_is_never_the_real_host_and_never_doubles`, `registry_includes_demo`, `registered_bench_ids_lists_linked_benchers_sorted`, `single_core_pass_relabels_and_sets_flag`, `pass_request_env_parsing` |
| `crates/nornir-bench/src/assets.rs` | `ensure_returns_existing_without_download`, `ensure_creates_missing_cache_dir`, `default_cache_dir_env_override_then_home_fallback` |
| `crates/nornir-bench/src/diff.rs` | `throughput_drop_is_a_regression_rise_is_not`, `latency_rise_is_a_regression_and_tolerance_absorbs_wobble`, `neutral_and_static_metrics_never_gate`, `a_red_test_gates_even_with_no_scalar_drop`, `added_and_removed_benches_are_surfaced_not_regressions` |
| `crates/nornir-bench/src/history.rs` | `legacy_line_parses_without_timestamp_or_tests`, `new_line_roundtrips_with_tests`, `append_writes_committable_jsonl_receipt` |
| `crates/nornir-bench/src/legacy.rs` | `mbs_basic`, `missing_tool_is_none`, `comparison_pairs_metrics_and_speedup`, `comparison_without_legacy_is_ours_only` |
| `crates/nornir-bench/src/lib.rs` | `static_and_measured_are_distinguishable`, `measured_tag_is_omitted_on_the_wire_but_static_survives_roundtrip`, `legacy_line_without_source_reads_as_measured`, `unit_and_direction_from_name`, `explicit_override_wins`, `empty_history_is_red_not_silently_green`, `real_runs_are_sannr_newest_first_and_limited` |
| `crates/nornir-bench/src/progress.rs` | `mode_labels`, `starting_line_names_suite_and_mode`, `done_line_includes_summary_and_elapsed`, `telem_suffix_and_done_line`, `failed_line_carries_error`, `actionlog_append_matches_viz_format` |
| `crates/nornir-bench/src/selftest.rs` | `emits_honest_rows_for_bench_surfaces` |
| `crates/nornir-bench/src/telemetry.rs` | `proc_stat_parses_aggregate_and_cores`, `interval_utilisation_counts_busy_cores`, `zero_interval_is_ignored`, `metrics_roundtrip`, `sampler_runs_and_reports_on_a_busy_workload` |
| `crates/nornir-build-thing/src/analyze.rs` | `default_analyzer_is_noop` |
| `crates/nornir-build-thing/src/assemble.rs` | `each_kind_dispatches_and_output_kind_matches`, `lineage_has_provenance_dep_artifact_and_content_id_is_the_hash`, `precheck_missing_when_target_not_installed`, `composite_threads_lib_content_id_into_parent_dep_row`, `cycle_in_embeds_errors`, `idempotent_second_assemble_is_a_cache_hit`, `container_without_injected_builder_errors`, `dispatch_runs_injected_analyzer_keyed_by_content_id` |
| `crates/nornir-build-thing/src/build_info.rs` | `manifest_rows_cover_provenance_dep_and_artifact`, `feed_rows_carry_step_log_and_metrics`, `in_mem_sink_round_trips_rows`, `null_sink_is_a_noop`, `arrow_schema_has_f3_columns_in_order`, `arrow_batch_matches_schema_and_values`, `emits_functional_rows_for_build_info` |
| `crates/nornir-build-thing/src/capability_probe.rs` | `a_present_literal_reads_present`, `the_stub_marker_with_no_proof_reads_hollow`, `neither_proof_nor_stub_is_unknown_never_present`, `contains_literal_is_a_plain_substring_scan`, `gate_binary_fails_on_hollow_and_passes_once_the_capability_is_present`, `an_unreadable_path_is_a_failure_not_a_pass` |
| `crates/nornir-build-thing/src/container.rs` | `containerfile_materializes_deterministically`, `build_args_carry_tags_and_format`, `variant_deps_lists_chained_cargo_builds`, `validate_catches_missing_base_and_layer_source`, `descriptor_and_content_id_round_trip`, `image_format_parses_and_round_trips_all_spellings` |
| `crates/nornir-build-thing/src/elm.rs` | `three_holger_exes_become_three_cards`, `build_emits_a_run_effect_and_marks_building`, `success_feeds_back_a_provenance_summary`, `invalid_variant_refuses_to_build`, `model_state_json_round_trips` |
| `crates/nornir-build-thing/src/feature_axis.rs` | `an_offered_feature_no_variant_names_is_the_gap`, `declaring_the_variant_closes_the_gap`, `a_required_features_bin_no_variant_satisfies_is_named`, `an_unread_manifest_is_reported_as_unmeasured_not_as_clean`, `an_unconstrained_repo_is_still_measured_against_its_manifest`, `a_manifest_scan_that_missed_a_sub_workspace_says_so`, `manifest_features_reads_a_workspace_and_drops_default`, `state_json_carries_the_gap_for_a_pane`, `the_gate_catches_korps_real_orphaned_features`, `documenting_the_debt_in_allow_unbuilt_passes_the_gate`, `an_undocumented_orphan_beside_documented_debt_still_fails`, `a_stale_allowlist_entry_fails_the_gate`, `the_real_constellation_feature_rot_gate`, `gate_all_names_every_rotting_repo_at_once` |
| `crates/nornir-build-thing/src/gating.rs` | `real_constellation_gating_report`, `cfg_forms_parse_and_undecidable_ones_say_so`, `compiled_in_gated_out_and_undecided_are_three_different_answers`, `an_unscanned_repo_never_reads_as_nothing_gated`, `required_features_targets_are_found_and_are_the_headline_rot`, `augmenting_a_graph_never_creates_a_dangling_edge`, `the_file_cap_keeps_the_gated_out_ones` |
| `crates/nornir-build-thing/src/image_build.rs` | `build_container_seals_over_the_produced_image_digest`, `target_attestation_round_trips_and_tamper_is_detected`, `build_vm_seals_over_the_produced_kernel_and_rootfs_hashes`, `prebuilt_vm_builder_errors_when_no_artifact_present`, `prebuilt_vm_builder_stages_and_seals_the_from_variant_artifact`, `build_container_seal_with_dev_sealer_is_verifiable`, `build_vm_uses_the_builders_engine_label`, `build_container_propagates_builder_error_on_failed_outcome`, `build_iso_seals_over_the_produced_iso_and_from_variant_payload`, `pure_rust_builder_masters_a_real_bootable_iso`, `pure_rust_builder_errors_when_efi_boot_image_missing`, `build_container_default_engine_label_is_bollard`, `tar_context_stages_from_variant_artifacts`, `tar_context_errors_when_variant_not_staged` |
| `crates/nornir-build-thing/src/installer.rs` | `masters_a_uefi_installer_iso_chaining_the_pid1_variant`, `kvm_disk_args_attach_a_blank_target_and_optional_gpu`, `success_marker_and_disk_size_are_stable`, `validate_and_content_id_round_trip`, `empty_name_and_zero_disk_are_rejected`, `payload_kind_defaults_to_embedded_and_labels_s3`, `lean_carries_no_backend_and_bakes_the_lean_selector`, `each_fat_backend_bakes_its_selector_and_is_distinctly_content_addressed`, `container_backend_label_round_trips_and_knows_daemonless`, `declared_appliance_boot_param_is_not_double_stamped`, `installer_from_toml` |
| `crates/nornir-build-thing/src/iso.rs` | `master_args_carry_volume_output_and_boot_image`, `qemu_args_boot_the_iso_as_a_firmware_cdrom`, `variant_deps_lists_the_chained_cargo_build`, `validate_requires_a_bootable_payload`, `descriptor_and_content_id_round_trip`, `iso_format_parses_and_round_trips_all_spellings`, `iso_build_from_toml`, `iso_cell_boot_logo_override_parses_and_surfaces_on_the_same_wire`, `iso_cell_boot_logo_wins_over_the_from_variant_boot_logo` |
| `crates/nornir-build-thing/src/iso_writer.rs` | `masters_iso9660_structures_and_reads_payload_back`, `mastering_is_deterministic`, `unstaged_boot_image_errors` |
| `crates/nornir-build-thing/src/linkage.rs` | `an_unmeasured_linkage_audit_never_passes`, `a_thin_declaration_is_failed_by_what_it_links`, `a_fat_variant_is_not_failed_for_being_fat`, `an_embedded_variant_that_embeds_nothing_makes_the_thin_check_vacuous`, `a_declared_inapplicable_axis_holds_but_silence_is_audited_as_fat`, `the_forbidden_list_names_entry_points_not_products`, `a_missing_artifact_is_no_audit_at_all`, `object_reads_a_real_binary_and_extra_families_are_honoured`, `human_bytes_reads_like_a_size` |
| `crates/nornir-build-thing/src/output.rs` | `service_kind_outputs_service_spec`, `exe_and_lib_output_binary_artifact`, `wasm_kind_outputs_pages_bundle`, `container_kind_outputs_image_ref`, `vm_kind_outputs_vm_ref` |
| `crates/nornir-build-thing/src/plugin.rs` | `the_default_validate_applies_the_declared_rules`, `an_apps_own_artifact_rule_runs`, `dispatch_answers_every_op_in_json`, `a_malformed_argument_is_refused_not_trapped`, `the_abi_pack_round_trips`, `a_refusal_can_never_be_silent` |
| `crates/nornir-build-thing/src/plugin_host.rs` | `a_module_asking_for_a_capability_is_refused_by_name`, `a_module_speaking_another_abi_is_refused`, `a_module_without_memory_is_refused`, `an_infinite_loop_exhausts_fuel_instead_of_hanging_the_host` |
| `crates/nornir-build-thing/src/progress.rs` | `a_channel_is_an_observer_and_a_closed_one_is_harmless`, `a_failure_keeps_the_compilers_own_words`, `only_a_real_observer_asks_cargo_for_the_diagnostics`, `events_round_trip_through_json_for_a_panes_state` |
| `crates/nornir-build-thing/src/provenance.rs` | `attestation_captures_variant_inputs_host_and_hashes`, `record_round_trips_through_json`, `attestation_digest_is_stable_and_round_trips`, `tamper


(The README goes on at https://gunnar.rs/nordisk/edda/readme.md.)

## nordisk/facett

At https://gunnar.rs/nordisk/facett.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<div align="center">

###### [ ⚡ Live Web Demo ](https://nordisk.codeberg.page/facett)

*every facett component, every palette — running in your browser (egui → WebAssembly)*

</div>

---

#### facett

> The **egui component framework** for the constellation's UIs — reusable,
> source-agnostic **data-viewing components** (*each a facet of your data*): hand
> one Arrow / a graph / geo and it draws it fast. Build your own [korp] on it —
> **korp and nornir both do.**

##### Quickstart — run the showcase

```sh
cargo install facett-demo --locked   # from crates.io
facett-demo                          # launch the showcase eframe app
```

Every facett component with realistic data, switchable across all palettes (needs a display — or try the browser demo linked above). `cargo install facett-demo` also ships an `osm3d_demo` bin for the 3D/OSM view.

The Falsett tab includes synthetic hub, stage and layered-DAG fixtures. Pipeline,
query-plan and Brokkr DAG components share Falsett's persistent GPU/CPU rendering,
with their own data models, overlays and navigation. Embedding hosts enable the
component's `wgpu` feature and install `facett_flow::gpu::install_renderer` on
their render state; see [the pipeline guide](.nornir/pipeline-guide.md).

##### Mashup — one Facet kernel, every kind of data view

Every component implements a single `facett_core::Facet` trait (`title` / `ui` /
`state_json`) and drops into a `FacetDeck` — so one source-agnostic kernel draws
them all, and a test can drive any of them headless:

- **Source-agnostic** — facett renders *what it's handed*; no connection or query logic (that stays in the data layer, e.g. knut).
- **Flagship `facett-grid`** — Arrow-backed, virtualized rows+cols at **60 fps over 1M+ rows**, frozen header + pinned cols + Σ footer, Excel-style selection→TSV.
- **Geo · 3D · graphs** — a **Garmin-style car-nav basemap** (road-class hierarchy · casing · one-way chevrons · route ribbon · priority labels) over GeoParquet (`facett-osm` at **6.3M pts/s**), a z-buffered lit **3D city** (CPU or wgpu), 3D graphs + a glTF model viewer, circular + force-directed layouts.
- **Falsett flow graphs** — animated directed curves through a translucent hub, explicit group overview and resident GPU geometry; [component and host integration](facett-flow/README.md).
- **wgpu fast path** — a GPU viewport-cull + indirect-draw kernel lands behind the same `draw()`/`ui()` call.
- **Themeable & headless-tested** — `Theme::sci_fi()`, self-tested against the MEGA matrix + perf harness.

##### Read the real docs

- 📖 **[The manual — `docs/manual.pdf`](docs/manual.pdf)** — every component chapter (kernel, grid, map, graph, docview, app framework): design + guide, the crisp core.
- 🔰 **[The idiot guide — `docs/idiot_guide.pdf`](docs/idiot_guide.pdf)** — zero-knowledge, cold-shell-to-green walkthroughs.
- 🧠 **[Reasoning &amp; record — `docs/unsorted.pdf`](docs/unsorted.pdf)** — the *why*, the rejected alternatives, and the history.
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the full pitch, the complete crate catalog, and usage.

##### License

MIT — see [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE). Versions published up to and including 0.2.0 (crates.io 0.1.x/0.2.0 and PyPI facett/facett-demo 0.2.0) were released under MIT OR Apache-2.0 and stay so. From 0.2.1 facett is MIT only.

Credits: see [`CREDITS.md`](CREDITS.md).

[korp]: https://git.gunnar.rs/nordisk/korp


## nordisk/frida

At https://gunnar.rs/nordisk/frida.

### README.md

#### frida

**The EU eID — eIDAS 2.0 / EUDI Wallet, done in Rust.**

`frida` (from Old Norse *friðr*, "peace") is Nordisk's open, EU-aligned electronic-identity
service: a peer to Freja/BankID built on the European standard stack so a `frida` credential works
across the EUDI ecosystem — not a vendor silo. Two credential holders:

- **Mobile app** — hardware-backed device key (Secure Enclave / StrongBox), user-verified.
- **Web + card reader** — smartcard over **PKCS#11**; the card signs, the host never sees the key.

##### What it is, concretely

A **Rust gRPC service** (issuer + verifier) that runs two ways:

- **Standalone (single server)** — one high-speed binary (tonic/tokio) over an **embedded**
  key-value store (**redb** or **rocksdb**). No database service to stand up.
- **Kubernetes (multi-server)** — a **`frida` operator** (CRDs: `Issuer`, `Verifier`,
  `TrustRegistry`) provisions and scales nodes; state is an **append-only event log with
  acknowledgement (ACK)** across nodes, and large transactional/audit volume goes to **skade**
  (the pure-Rust Iceberg lakehouse). WireGuard secures node-to-node traffic.

**No relational database, ever.** Persistence is a `Store` trait (in `edda`, following the existing
`nornir-warehouse-trait` pattern) with two backends: embedded **redb/rocksdb** for single-server,
and **skade** for large transactional/analytical volume. See [`docs/PROTOCOL.md`](docs/PROTOCOL.md).

##### Capabilities (auth + sign + the nice-to-haves)

| capability | standard | note |
|---|---|---|
| **authentication** | OpenID4VP | prove identity; pseudonymous per-RP option |
| **signing** | OpenID4VP over data | incl. **QES** (qualified e-signature) via a remote-signing path |
| **credential issuance** | OpenID4VCI | mint PID + attribute credentials |
| **selective disclosure** | SD-JWT VC | present just `age_over_18`, name, etc. |
| **mobile driving licence / mdoc** | ISO/IEC 18013-5 | proximity + online presentation |
| **revocation / status** | status lists | credential validity without phoning home per check |
| **mandates / representation** | eIDAS 2 | sign on behalf of a company |
| **age verification** | SD-JWT / mdoc | high EU demand, privacy-preserving |
| **audit receipts** | signed logs | non-repudiation for auth/sign events |

`auth` and `sign` are the core; the rest are the "what more is nice" — issuance, selective
disclosure, mandates, and QES are the ones that make it a real EUDI player.

##### Who uses it

Vetra's **`heimdal`** integrates `frida` as a provider (`heimdal-frida`) behind its
`IdentityProvider` trait, alongside Freja, BankID, and `tyr`. Anyone else can integrate `frida`
too — it speaks the open EU standards.

##### Repo layout (planned)

```
spec/                the credential profiles + gRPC service contract (the product)
proto/               .proto definitions (tonic)
crates/
  frida-types/         shared credential/envelope types (COSE/JOSE, SD-JWT VC, mdoc)
  frida-issuer/        OpenID4VCI issuer
  frida-verifier/      OpenID4VP verifier
  frida-server/        standalone gRPC binary
  frida-operator/      Kubernetes operator (kube-rs)
```

Types shared with **`tyr`** (crypto primitives, credential envelopes) are expected to live in
**`edda/`** rather than being duplicated here.

##### Status

**Design.** No protocol code yet — see [`docs/PROTOCOL.md`](docs/PROTOCOL.md).

##### License

**0BSD** (Zero-Clause BSD) — the most permissive option there is: use it for anything, no
attribution required. See [`LICENSE`](LICENSE).


## nordisk/gunnar

At https://gunnar.rs/nordisk/gunnar.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<!-- nornir:class: readme -->

#### gunnar

<img src=".nornir/assets/gunnar.webp" alt="gunnar" width="320">

**Fast setup:** [the guide](.nornir/gunnar-quickstart.md) · [PDF](docs/gunnar-fast-setup.pdf) — four lists of commands, one picture per setup.

**Features:** [what is new, with status, source path and commit](docs/FEATURES.md).

Git server in Rust, with two engine variants: znippy-based (Apache Arrow IPC in
a znippy archive) or gix-based (traditional filesystem).

* Built-in replication to a twin gunnar: self-hosted, the twin is any box that reaches the primary (`gunnar control twin join`); a gunnar.rs instance's twin runs in the same zone as its main.
* Vetra hardware appliance variant: OS-less, hardened kernel.
* Free to host your own.
* The exact same code is hosted at https://gunnar.rs
* NVMe, io_uring, XFS on raid0, OS-less appliance kernel, WireGuard, Prometheus, Git LFS, smart HTTP, OCI, webhooks, replication to a twin, sshsig/ed25519 signed pushes, SLSA-shaped provenance, transparency log.

##### Building

Anyone can clone the repository read-only, with no registered SSH key:

```
git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/gunnar.git
cd gunnar
cargo build --release -p gunnar-cli
```

A lone clone builds the server by itself, with nothing beside it:

```
git clone ssh://git.gunnar.rs:2222/nordisk/gunnar && cd gunnar && cargo build --release
./target/release/gunnar        # serves on loopback; http://127.0.0.1:8080/healthz answers ok
```

`cargo build --release` at the root builds the default member, `gunnar` (the
server: engine-znippy, SSH and smart HTTP with TLS via `--http-tls-*`; no mimalloc, no company SSO, no monetize: `--features mimalloc,sso` adds those). The committed
`Cargo.lock` is the crates.io lock, so `cargo build --locked` and `cargo install --locked
--path crates/gunnar-cli --features client` work from a lone clone too (`cargo xtask
lone-clone` proves it on every push). The root takes no sibling patch in any checkout.
In an estate checkout, with `../edda`, `../znippy`, `../Skidbladnir`, `../tunnr`,
`../draupnir` and `../monetize` beside it, the detached estate workspaces
(`gunnar-front`, `gunnar-monetize-guest`, the benches, `installtest`) build against
those siblings through the `[patch.crates-io]` in their own manifests, with
`--manifest-path`.

This page documents **gunnar 1.5.1** — the version `gunnar --version` prints.

License: AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS (Rickard Ernst Björn Lundin). Commercial
terms: `COMMERCIAL-LICENSING.md`.

##### Install the server with cargo

```
cargo install gunnar
gunnar
```

A bare `gunnar` serves on loopback with no configuration: SSH on `127.0.0.1:2222`,
smart HTTP on `127.0.0.1:8080`, and health at http://127.0.0.1:8080/healthz. The
default build compiles no monetize crate; the other variants are opt-in cargo
features, listed in [`crates/gunnar/README.md`](crates/gunnar/README.md).

##### Release provenance (SLSA Build L3)

Releases are built by dwarves, the build server, in its isolated release-build mode and carry signed SLSA v1 provenance:
one in-toto statement per release, every artifact by its sha256, signed on the owner's
YubiKey. The bundles are in [`attestations/`](attestations/), the release key in
[`slsa/`](slsa/); how to verify (cosign, dwarves or Holger) and what still keeps us below
L3: [docs/SLSA.md](docs/SLSA.md).

##### Complexity and measurements

`n` objects, `r` refs, `e` refs in a batch, `k` batch size, `s` object size,
`|W|`/`|H|` want/have closure. Read off the znippy engine's code.

| operation | znippy |
|---|---|
| `put_pack` | `O(bytes)`, index deferred |
| `put_refs` | `O(r) + O(e·log n)` |
| `get` | `O(log n) + O(s)` |
| `has` | `O(log n)` |
| `size` | `O(log n)` |
| `extents` | `O(k·log n)` |
| `refs` | `O(r)` |
| `update_ref` | `O(log r)` |
| `head` | `O(log r)` |
| `header` | `O(log n)` |
| `reachable` | `O(1)` map lookup + bitmap OR |
| `gc` | `O(n)` compact |

Measured. Each row names its date. A dash means no measurement exists.

| measurement | gitea | gix | znippy | date |
|---|---|---|---|---|
| clone, serve wall, 16384 commits | — | 17.7 s | 7.23 s | 2026-09-01 |
| push, receive-pack, 16384 commits | — | 0.80 s | 1.54 s | 2026-09-01 |
| RSS floor, 16384 commits | — | 13.6 MB | 29.2 MB | 2026-09-01 |
| `reachable`, 16384 commits | — | 14.9 s | 5.6 ms | 2026-09-01 |
| `refs` | — | — | 30–50× faster than gix | 2026-09-01 |
| `header` | — | ~5× faster than znippy | — | 2026-09-01 |
| durable 8 KiB push, index included | — | 50 573 µs (library writer, no fsync) | 147.7 µs | 2026-08-07 |
| provision 100 000 users | 77 min, projected | — | 6.1 s | 2026-08-12 |
| ls-remote p50 @ 100 000 users | 27.0 ms | — | 10.0–10.5 ms | 2026-08-12 |
| ls-remote p99 @ 100 000 users | 39–43 ms | — | 102–168 ms | 2026-08-12 |
| ls-remote throughput | 1 140 req/s | — | 2 060–2 320 req/s | 2026-08-12 |
| clone (small), net CPU | 0.033 s | — | 0.010 s | 2026-08-13 |
| clone (small), peak RSS | 113 MB | — | 2 308 MB | 2026-08-13 |
| push (base), net CPU | 0.944 s | — | 0.302 s | 2026-08-13 |
| push (base), server→client bytes | 2.1 kB | — | 1.4 kB | 2026-08-13 |

Conditions: 2026-09-01 rows, the desktop, 5 repetitions plus warm-up, load-1
mean 13.4, both engines in one binary. 2026-08-13 rows, the desktop, 32 cores,
3 interleaved rounds, 8 CPUs and 8192 MB per engine, byte counts taken by a
counting relay. 2026-08-12 and 2026-08-07 rows, the desktop.

##### Thanks

* Its inventors: Vetra Automation AB and Ignalina ApS (Danskjävlarna).
* Ragnar Groot Koerkamp — the ridiculously fast static search tree.
* Christopher Berner — the neat redb.

##### Contributing, security and changes

- [CONTRIBUTING.md](CONTRIBUTING.md): how to get the code, report a bug and propose a change.
- [SECURITY.md](SECURITY.md): how to report a vulnerability privately (gunnar@gunnar.rs).
- [CHANGELOG.md](CHANGELOG.md): what changed in each release, and the vulnerabilities each one fixed.

##### Sponsors

None so far — we would love to have one. Contact gunnar@gunnar.rs.

AI-generated details: [.nornir/detail.md](.nornir/detail.md)


## nordisk/gunnar-ui

At https://gunnar.rs/nordisk/gunnar-ui.

### README.md

#### gunnar-ui

<img src="crates/gunnar-ui-web/assets/gunnar.webp" alt="gunnar" width="320">

The gunnar console: a passkey-guarded web UI (Leptos, WebAssembly) and its
backend-for-frontend, which speaks sshsig-signed gRPC to a gunnar appliance.

* Anonymous browsing of public repositories.
* Mirrors from GitHub, Codeberg, Forgejo and GitLab.
* Accounts, keys, groups and grants.
* Usage and paid signup through a monetize server, compiled in by the `metered` feature; the open build has no money code.
* The exact same code is hosted at https://gunnar.rs

##### Building

```
git clone ssh://git.gunnar.rs:2222/nordisk/gunnar-ui
cd gunnar-ui
cargo build --release -p gunnar-ui-server
```

The console itself is a WebAssembly bundle, built by trunk, which is required:

```
cd crates/gunnar-ui-web
trunk build --release
```

This page documents **gunnar-ui 1.5.0** — the version `gunnar-ui-server
--version` prints.

License: AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS (Rickard Ernst Björn Lundin). Commercial
terms: `COMMERCIAL-LICENSING.md`.

##### Routes

The console's pages, routed in the browser. A visitor has no session; signed in
is any passkey session; admin is the role a session carries.

| page | who may open it |
|---|---|
| `/repos` | visitor |
| `/repo/{namespace}/{name}` | visitor |
| `/repo/{namespace}/{name}/files` · `/graph` · `/diff` · `/blame` · `/search` · `/chain` | visitor |
| `/repo/{namespace}/{name}/pulls` · `/pulls/{n}` · `/issues` · `/issues/{n}` | visitor |
| `/` | signed in |
| `/storage` · `/usage` · `/usage/{namespace}` · `/events` | signed in |
| `/account` | signed in |
| `/access` | signed in; every mutation on it is admin |
| `/mirrors` · `/mirrors/import` | signed in; every mutation on it is admin |

The server's own routes, read off `serve.rs`. Everything under `/api/` that is
not listed as a visitor's is refused without a session; the role floor is
decided by method and prefix in one place (`auth::middleware::role_floor`).

| route group | who may open it |
|---|---|
| `/health` · `/login` · `/login/repos` · `/auth/status` · `/auth/logout` | visitor |
| `/auth/passkey/login/*` · `/auth/passkey/register/*` | visitor |
| `/welcome` · `/auth/signup/status` (a `metered` build that sells) | visitor |
| `/signup` — the ONE sign-up page: `?plan=private` a new private Gunnar, `?plan=10gb` an account here (or, signed in, growing it); `/private` redirects to it · `/auth/private/ladder` · `/auth/private/{quote,start,status}` · `/auth/signup/{ladder,name,start,rename}` (a `metered` build that sells) | visitor; a signed-in buyer's order is theirs (the session's name, locked) |
| `GET /api/repos` | visitor |
| `GET /api/repos/{namespace}/{name}` and its `refs` · `head` · `log` · `browse` · `diff` · `blame` · `search` · `object` · `chain` leaves | visitor |
| every other `GET`/`HEAD` under `/api/`, including `/api/events` | signed in |
| `/api/account/keys` — a person's own keys | signed in |
| every other mutation under `/api/` | signed in, operator |
| `/api/principals` · `/api/keys` · `/api/grants` · `/api/namespaces` · `/api/accounts` · `/api/forges` · `/api/mirrors`, mutating | admin |
| `/api/namespaces/{name}/increase` (a `metered` build holding a monetize bearer) | signed in to read, admin to spend |
| `/auth/passkey/invites` · `/auth/passkey/credentials` | signed in, a fresh ceremony to mint or revoke; gunnar decides who may invite whom and whose passkeys a person may see |
| `/auth/sessions/revoke-all` | admin |

##### Passkeys, invites and sessions live in gunnar

The console keeps no passkey, no invite and no key of anybody's. gunnar
issues the WebAuthn challenge, verifies every login against the passkey it
stores on the person's principal (`Access.WebLogin`), and enrols a short-lived
session key the console minted for that login; everything the session does is
signed with that key, as the person, and gunnar's grants decide. Logging out
revokes the session key. Registration (`Access.WebRegister`) redeems an invite
gunnar minted (`Access.CreateInvite`, asked as the person who invites), or is
the operator's first passkey through the installer's break-glass token. A
passkey this console registered before 2026-10-04 moves to gunnar when the
console starts. The console's own store keeps cookie sessions, preferences and
caches only.

##### Thanks

* Its inventors: Vetra Automation AB and Ignalina ApS (Danskjävlarna).
* Greg Johnston and Ben Wishovich — Leptos.
* Anthony Dodd and Jens Reimann — Trunk.

##### Sponsors

None so far — we would love to have one. Contact gunnar@gunnar.rs.


## nordisk/holger

At https://gunnar.rs/nordisk/holger.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<p align="center">
  <img src=".nornir/assets/holger-znippy-logo.png" alt="holger" width="320" />
</p>

#### Holger guards your artifacts at rest

**Immutable artifact repository in pure Rust.**

⚡ **GATLING** — one all-core engine · zero rayon · zero-copy · ~11/12 cores

- **Airgap**: transfer from internet, Nexus, Artifactory → immutable znippy archive
- **Versioned**: atomic transactions, content-addressed (Blake3), O(1) file random access
- **Extreme performance**: Apache Arrow zero-copy, multi-core parallel I/O
- **Pure Rust**: no OS packages, no shell, no Python (even decompressors rewritten from C)
- **Embeddable**: runs on bare-metal/OS-free devices, 2 MB RSS

##### Build and install

Anyone can clone the repository read-only, with no registered SSH key:
`git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/holger.git`. The commands below use
the `git@` address, which needs a registered key; the anonymous address works the same way.

```bash
git clone ssh://git@git.gunnar.rs:2222/nordisk/holger.git && cd holger && cargo build --release
target/release/holger
```

A plain `cargo build --release` at the root builds the server package only
(`default-members`) and links one binary, `target/release/holger`: the small
`server-local` build with the intake chain (the steps and their scanners), and no
bench, no wasm plugins, no `online` and no metered. No Build Thing and no xtask are needed, and nothing has to sit beside the
clone: the root workspace is the open core, so a lone clone resolves every crate from
crates.io, and `cargo install --path server/cli` installs the same server
(`cargo xtask lone-clone` proves both end to end with `--locked`, `/healthz` included; until
the open sibling crates it lists are on crates.io, `--stage` packages them from the siblings).

The estate, appliance and build-description crates (holger-fleet, holger-live, the build
declaration, xtask, the monetize guest and the estate tests) are members of the separate
`estate/` workspace (`cargo test --manifest-path estate/Cargo.toml --workspace`), which
builds only in an estate checkout.

In an estate checkout, with `../edda`, `../Skidbladnir`, `../monetize`, `../tunnr`,
`../draupnir`, `../modgunn` and `../facett` beside it, run `cargo xtask siblings`
once after the clone (every other `cargo xtask` verb refreshes it too): it copies
`.cargo/estate-siblings.toml` to the uncommitted `.cargo/siblings.toml`, which
`.cargo/config.toml` includes when it is there, and switches the sibling patches on.
`cargo xtask` starts in any checkout: its launcher (`xtask/boot`) is a detached
workspace with no dependency. The tests against the real ONE box are
`estate-tests/`, an `estate/` member (`cargo test --manifest-path estate-tests/Cargo.toml`),
which needs those siblings.

Install it instead:

```bash
cargo install --path server/cli
cargo install --git ssh://git@git.gunnar.rs:2222/nordisk/holger.git holger
cargo install holger
holger
```

The last `cargo install` line installs the release from crates.io (Holger 2.0.0 and later).

The on-prem downstream gateway (Holger v2.0 H3) is the same package with its own feature
set and binary, exactly the Build Thing's `downstream` row:
`cargo install --locked --path server/cli --no-default-features --features downstream --bin holger-downstream`. It takes packages only from its one upstream Holger (a signed
bundle on a stick, or a pull), verifies them, serves Maven, cargo, Python (PEP 503/691),
OCI and Go, blocks what its upstream invalidates, and dials nothing else.

The first `holger` creates `~/.holger/holger.ron` (or `$HOLGER_DATA/holger.ron`) and
starts the server on loopback:

- health is `http://127.0.0.1:18444/healthz`,
- cargo reads `sparse+http://127.0.0.1:18444/cache/index/` and publishes to
  `/sparring` (with `--features online`, `/cache` is a caching mirror of crates.io),
- `http://127.0.0.1:18444/` is the front page with `--features front`.

To run it as a service: `sudo ~/.cargo/bin/holger install add` (systemd), or the Helm
chart with its scan-worker sidecar (Kubernetes). The exact commands for each flavour are
in **[Holger v2.0 — Run it: three flavours](docs/HOLGER-V2.md#run-it-three-flavours)**.

The default build is small on purpose: the server, HTTPS (rustls; set `ron_tls` on
an endpoint), the static bearer token and mTLS, the raw and rust (cargo)
repositories, and the intake chain (`secure_update`, which carries `supply-chain`). `online`, `auth-oidc`, `auth-oauth2`, `auth-passkey`, `front`, `sign`
and the other formats (`fmt-all`, `fmt-npm`, …) are opt-in features.

The next start keeps your edited `holger.ron`. `holger start --config <file>` runs any
other config; `holger --help` lists every verb. `--features holger-server-alias` also
builds the same program under its old name, `holger-server`. The default build links no
monetize crate (a test runs `cargo tree -p holger -e normal` to prove it).

###### The server variants

Every server variant row (`holger-recipe/src/variants.rs`) is a cargo feature set
on the same `holger` package. Metered and shop variants are opt-in only.

| variant row | install line |
|---|---|
| `server-local` (the default) | `cargo install holger` |
| `upstream` (the OPEN delivery's default) | `cargo install holger` |
| `downstream` (the on-prem gateway alone) | `cargo install holger --no-default-features --features downstream --bin holger-downstream` |
| `embedded` | `cargo install holger --no-default-features --features embedded,sign,fmt-all,jobs,supply-chain,admission,mirror --bin holger-embedded` |
| `mini` | `cargo install holger --no-default-features --features mini,appliance,sign,fmt-all,jobs,supply-chain,admission,mirror --bin holger-mikro` |
| `full` | `cargo install holger --no-default-features --features full,front,sign,fmt-all,jobs --bin holger-full` |
| `mvp-server` | `cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,allow-meter,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,mirror --bin holger-mvp` |
| `mvp-server-musl` | `cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,allow-meter,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,mirror --bin holger-mvp --target x86_64-unknown-linux-musl` |
| `mvp-server-metered` (opt-in, metered) | `cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,metered,admit-share,front,wasm-plugins,meter-guests,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,intel-fetch,mirror --bin holger-mvp-metered` |
| `mvp-server-musl-metered` (opt-in, metered) | `cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,metered,admit-share,front,wasm-plugins,meter-guests,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,intel-fetch,mirror --bin holger-mvp-metered --target x86_64-unknown-linux-musl` |
| `mvp-server-musl-metered-narrate` (opt-in, metered, DEV boot log) | `cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,metered,admit-share,front,wasm-plugins,meter-guests,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,intel-fetch,mirror,dev-narrate --bin holger-mvp-metered --target x86_64-unknown-linux-musl` |
| `downstream-musl` (a downstream's medium, OX-WGDS3) | `cargo install holger --no-default-features --features downstream,downstream-wg,paired --bin holger-downstream --target x86_64-unknown-linux-musl` |

The client side is not in this package: `holger-agent-cli` and `holger-update-bundle`.

###### The open rows: the Build Thing builds the `cargo install` line (Holger v2.0 H5)

The Build Thing rows of the open products build exactly what one plain `cargo install`
line builds: the same package, bin, features, default switch, target and release profile
(`holger-recipe/src/twins.rs`, held by `holger-recipe/tests/rows_equal_cargo_install.rs`).
`cargo xtask twin` builds each row and its line in the pinned toolchain image and compares
the two binaries: byte-identical for all three. holger-ui's `ui` row is
`cargo install --locked --path crates/holger-ui-server` in holger-ui.

| product | Build Thing row | `cargo install` line | crates | binary | adds over the default |
|---|---|---|---|---|---|
| upstream | `upstream` | `cargo install --locked --path server/cli` | 332 | 50.4 MB | nothing: it is the default |
| downstream | `downstream` | `cargo install --locked --path server/cli --no-default-features --features downstream --bin holger-downstream` | 329 | 50.1 MB | the gateway rules, sync and the Maven, Python, OCI and Go formats; it drops the scanners and the export |
| suite-mvp | `mvp-server` | `cargo install --locked --path server/cli --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,allow-meter,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,mirror --bin holger-mvp` | 408 | 61.2 MB | the estate: pairing, the appliance ceremony, passkeys, the built-in allow-meter, npm, the job queue, admission and the mirror |
| cli | `cli` | `cargo install --locked --path holger-cli` | 84 | 9.8 MB | the thin gRPC client: every console action as a command; no server, it dials only --at |


<!-- Static snapshot (v0.1.0, oden). The nornir "mashup" docs-render section is
     newer than the installed nornir (0.4.25), which errors on it; this table is
     kept static instead. Restore the auto-rendered mashup gen-block once a
     mashup-capable nornir is on PATH. -->
> **Biggest gap — `p50_ms`: holger leads naive by 785.3×** (0.15 vs 118.82).

**v0.1.0 · oden · 32 cores · 2026-07-23**

| workload | ops_per_sec | plans_per_sec | records_per_sec | p50_ms | p99_ms | p999_ms | iters | metadata_mbs | served_mbs | mean_ms |
|---|---|---|---|---|---|---|---|---|---|---|
| holger | **181863** | **761.95** | **3.8097M** | **0.15** | **0.46** | **1.10** | 2000 | **348.79** | **108.43** | **0.17** |
| artifactory | 20578 | | | 1.10 | 6.11 | 37.63 | | | 12.24 | 1.53 |
| dryrun | | | | | | | 100 | | | |
| naive | | 8.38 | 0.0419M | 118.82 | 131.88 | | 24 | 3.84 | | 119.35 |
| nexus | 15832 | | | 1.40 | 7.96 | 62.64 | | | 10.60 | 2.00 |

<sub>holger vs **Nexus** vs **Artifactory**, served over HTTP on all sides, measured
identically. **Fair hardware — no throttled rival:** each JVM competitor gets **every
core on the host** and ~75 % of its RAM as a fair JVM heap; holger runs in-process on
the same cores. The host and core count these numbers were taken on are stamped in the
table header above and in the provenance block below — never assume a number from one
host is comparable with one from another. Full method + every metric:
[`README-full`](.nornir/README-full.md).</sub>

**Which rival versions these are, and what they can serve.** The numbers above
were taken against **Sonatype Nexus 3.72.0** (released 2024-09-04) and **JFrog
Artifactory OSS 6.23.13** (released 2021-02-21). Two limits follow from that and
are stated here rather than left for the reader to discover:

* **Artifactory OSS serves Maven/Gradle/Ivy/SBT and generic repositories only —
  no Cargo, no PyPI, no npm, in any free JFrog edition.** No version fixes this.
  Every Artifactory row is a maven/generic data point.
* **Nexus 3.72.0 predates Cargo support**, which arrived in 3.73.0 (2024-10-10).
  A 3.72.0 server cannot serve Rust crates at all, so no row above supports a
  claim about holger versus Nexus on Rust crates.

The bench suite now also runs both rivals at their **current** tags
(Nexus 3.96.2, Artifactory OSS 7.161.26) as separate arms, so the modern
comparison exists beside the historical series rather than replacing it. Method
and every version fact: [`10-benchmark-baseline`](.nornir/stinky/10-benchmark-baseline.md).

###### Where these numbers come from

<!-- Static snapshot (v0.1.0, oden) — see the note on the `mashup` block above;
     `bench_status` is likewise newer than the installed nornir. -->
**v0.1.0 · oden · 32 cores · 2026-07-23**

| | |
|---|---|
| host | `oden` · 32 cores |
| version | `0.1.0` |
| measured | 2026-07-23 |
| arms | 16 |
| tests | 16/16 passed |
| publishable | yes |

The `mashup` and `bench_status` tables above are a static snapshot of the v0.1.0
run (the installed nornir predates those two `docs render` sections). The rest of
this page is still rendered by `nornir docs render holger`; refresh these two by
hand at release time, or restore their `nornir:gen` blocks once a newer nornir is
on PATH.

##### Release provenance (SLSA Build L3)

Releases are built by dwarves, the build server, in its isolated release-build mode and carry signed SLSA v1 provenance:
one in-toto statement per release, every artifact by its sha256, signed on the owner's
YubiKey. The bundles are in [`attestations/`](attestations/), the release key in
[`slsa/`](slsa/); how to verify (cosign, dwarves or Holger itself) and what still keeps us below
L3: [docs/SLSA.md](docs/SLSA.md).

##### Tier 1 in five minutes

Tier 1 is the server from a plain `cargo build` plus one RON file that says where
packages come from (`sources:`), what they pass on the way in (`steps:`) and the
rules that hold for every source (`rules:`). There is no UI and no SVG in tier 1.

**1. Clone and build.** The plain build carries the intake chain (the steps, the
scanners and the offline advisory import). It does not carry `online` or monetize.

```bash
git clone ssh://git@git.gunnar.rs:2222/nordisk/holger.git && cd holger
cargo build --release
```

Every scan runs in a container, never in the server (`rules: (sandbox: required)`, the
default): mint the scan worker image once. It needs rootless podman; without it Holger
refuses every scan by name (`holger-sandbox-1`).

```bash
cargo xtask scan-worker image     # localhost/holger-scan-worker:0.1.0, FROM scratch
```

**2. Write `holger.ron`.** Four raw repositories hold the steps, one upload source
brings packages in, and four tokens hold the duties (an Importer, an approver, a
repository admin and an auditor).

```ron
(
    exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:50051", ron_http_url: Some("127.0.0.1:8080")) ],
    storage_endpoints: [ (ron_name: "disk", ron_storage_type: "znippy", ron_path: "/tmp/holger/store") ],
    repositories: [
        (ron_name: "incoming",   ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/incoming")),
        (ron_name: "quarantine", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/quarantine")),
        (ron_name: "approved",   ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/approved")),
        (ron_name: "production", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/production")),
    ],
    audit: (dir: "/tmp/holger/audit", enabled: true),
    auth: Some((
        methods: [
            StaticToken(subject: "ida", token: Some("ida-token-of-sufficient-length")),
            StaticToken(subject: "bob", token: Some("bob-token-of-sufficient-length")),
            StaticToken(subject: "adm", token: Some("adm-token-of-sufficient-length")),
            StaticToken(subject: "aud", token: Some("aud-token-of-sufficient-length")),
        ],
        duties: { "ida": [Importer], "bob": [SecurityApprover], "adm": [RepositoryAdmin], "aud": [Auditor] },
    )),
    sources: [
        (name: "team", kind: upload, gates: (license: (deny: ["AGPL-3.0-only"]), cve: (max: medium))),
    ],
    steps: (approvals: 1, unknown_license: Pass),
    rules: (require_signed: false, require_provenance: false, deny_licenses: ["GPL-3.0-only"]),
)
```

Two lines loosen the gateway's defaults for this demo, and both say so in the file:
`require_signed: false, require_provenance: false` lets an unsigned file in (the default
floor refuses anything without a verified signature), and `unknown_license: Pass` lets
a file that declares no licence through (the default holds it for review).

**3. Start it.**

```bash
target/release/holger start --config holger.ron
```

**4. Upload a package as the Importer.**

```bash
echo hello > demo-1.0.txt
curl -X PUT -H "Authorization: Bearer ida-token-of-sufficient-length" \
  --data-binary @demo-1.0.txt http://127.0.0.1:8080/incoming/demo-1.0.txt     # 201 INGESTED
```

**5. See its state**, with its source and its effective policy, over HTTP and with the CLI:

```bash
curl -H "Authorization: Bearer aud-token-of-sufficient-length" \
  "http://127.0.0.1:8080/-/intake/state?name=demo-1.0.txt"
HOLGER_TOKEN=aud-token-of-sufficient-length \
  target/release/holger intake state --server 127.0.0.1:50051 --name demo-1.0.txt
```

Walk it through the steps: `POST /-/intake/advance?name=demo-1.0.txt&verb=advance`
as `bob` to SCANNED, POLICY_CHECKED and APPROVED, then as `adm` to SEALED, as `bob`
to PROMOTED and as `adm` to AVAILABLE. Production then serves it at
`/production/demo-1.0.txt`. A refusal names the rule and who set it, for example
``rule `deny_licenses` (source 'team') for source 'team': licence AGPL-3.0-only is denied``.

###### The RON shape

- **`sources:`** is a list. Each entry has a `name`, a `kind` and the fields its kind takes:

  | kind | what it is | build | notes |
  |---|---|---|---|
  | `upload` | packages pushed into the `incoming` repository (PUT, twine, npm publish) | default | `importers: [...]` limits who may upload as it. With several upload sources, the first path segment names the source: `PUT /incoming/<source>/<file>`. No upload source declared = one implicit source named `upload`. |
  | `holger` | an upstream Holger, mirrored on a timer | default | `url`, `repositories: [(ron_from, ron_to)]`, `credential`, `every_secs`. Its bytes skip the steps, so the floor must say `allow_unchecked_sources: true`. |
  | `crates` with `serves` | crates.io as a pull-through for the repositories in `serves` | `--features online` | The default build refuses it by name. Its bytes skip the steps (as above). |
  | `python`, `oci` | a PEP 503/691 index (PyPI, Chainguard Libraries) or an OCI registry (`cgr.dev`), pulled THROUGH the steps | `--features online,jobs` | `url`, `credential: (username, password_env)`, `select`, `max_files` (python), `platforms` (oci), `max_bytes`, `refresh_secs`, `auto_approve: off \| when(provenance: require, cve_max: medium)`. A fetched package is INGESTED as `pull-through:<name>` and walks the steps under its source's policy. |
  | `npm`, `maven`, `crates` with `url` | an npm registry (registry.npmjs.org, Chainguard Libraries for JavaScript at `libraries.cgr.dev/javascript/`), a Maven 2 repository (Maven Central, Chainguard Libraries for Java at `libraries.cgr.dev/java/`) or a cargo sparse index (`sparse+https://index.crates.io/`), pulled THROUGH the steps | `--features online,jobs` | As `python`, plus `wait_secs` (a request waits that long for its pull; default 30 s for `maven`, whose client does not retry a 503). npm: the tarball against `dist.integrity` (sha512); `dist.signatures` and the `dist.attestations` (SLSA provenance, publish attestation) go to the provenance verifier, the registry's keys in `rules.keys`. Maven: the POM and the JAR together, each against the `.sha512`/`.sha256`/`.sha1` beside it; Chainguard's `.slsa-attestation.json` beside the JAR goes to the verifier; PGP `.asc` is not checked. crates: the `.crate` against its index `cksum`; crates.io publishes no signature, so a floor with `require_signed: true` refuses every crate. Clients: `npm --registry <holger>/production/<name>/`, a Maven `<repository>` at `<holger>/production/<name>/`, cargo `registry = "sparse+<holger>/production/<name>/"`. The packument, `maven-metadata.xml` and the index list only AVAILABLE versions. |

- **`gates:`** on an upload source: `provenance: require(identity: (issuer, subject), slsa_min: 2) | prefer | off`,
  `sbom: require | off`, `malware: on | off`, `license: (deny: [...])`, `cve: (max: info|low|medium|high|critical)`,
  `block_formats: [...]`, `secrets: block | warn | off`. Every field is optional.
- **`steps:`** is the new spelling of `promotion:` with its `intake:` block: `incoming`,
  `quarantine`, `approved` and `production` (those are the defaults), `scan: [...]`,
  `approvals`, `transitions`, `inspect`, `advisory_db`, `allow_licenses`,
  `unknown_license`, `trust_policy`, `npm`, `secrets: (rules: "/path.ron")` (the secrets
  scanner's rule file; absent, the built-in set), and `gates: (...)`, the defaults every source starts from.
- **`rules:`** is the global floor: `require_signed`, `require_provenance`, `slsa_min`,
  `identities`, `builders` (`"id"` or `(id: "…", level: N)`), `keys`, `trust_root`,
  `require_sbom`, `malware`, `cve_max`, `secrets`, `deny_licenses`, `block_formats`,
  `allow_auto_approve` and `allow_unchecked_sources`. The provenance fields are the
  verifier's own (`holger-provenance`), with one parser.

A source's `gates` (and `steps.gates`) may only **tighten** the floor. A gate that would
loosen it, such as `provenance: off` under `require_signed: true` or `cve: (max: critical)`
under `cve_max: high`, stops the load by name, with the rule it breaks. An artifact's
effective policy is the floor plus its source's gates. It is fixed at ingest, kept on
every line of its intake record, and shown by `GetArtifactState`, `/-/intake/state` and
the audit trail. Every block refuses an unknown key by name, and so does the `auth` block.
The old shape (`promotion:` with `intake:`, `mirror:`, `rust-remote` repositories) still
loads unchanged; it has no floor. Naming `steps:` and `promotion:` together is refused.

**The default floor** (`rules:` absent, or a rule left out) is a security gateway's
default: it fails closed on what this build can check.

- `malware: on`: file inspection (archive bombs, path traversal, nesting, executables)
  and any configured ClamAV or YARA engine must pass.
- `cve_max: high`: an advisory rated critical is refused.
- `secrets: block`: the secrets scanner (in the scan sandbox) walks every package and
  image layer for private keys (PEM, OpenSSH, PGP, PuTTY), cloud credentials (AWS, GCP,
  Azure), tokens (GitHub, GitLab, Slack, Stripe, npm, PyPI, crates.io), `.env` files,
  kubeconfigs with credentials and high-entropy strings next to a secret-like name. A
  high or critical finding is refused by name; medium findings warn. A source may write
  `gates: (secrets: block)` over a `warn` floor, never the other way. The evidence names
  the rule (`secret:<id>`), the path and the line, never the secret. The rules are data:
  a `holger.secret-rules/v1` RON file (`steps.secrets.rules`), carried over the airgap
  like the YARA rules; the built-in set is `server/lib/src/scanner/secrets-rules.ron`.
- `block_formats: ["pickle"]`: the pickle family (`.pkl`, `.pickle`, `.pt`, `.pth`, `.joblib`) is refused at ingest.
- `allow_unchecked_sources: false`: a `holger` or `crates` source is refused at load.
- `require_signed: true` and `require_provenance: true`: the provenance verifier
  (`holger-provenance`: DSSE, in-toto, SLSA, Sigstore, PEP 740, npm, cosign) is in the
  build, so nothing without a verified signature and provenance comes in. The material
  waits beside the artifact by its sha256 (`<data_root>/holger-provenance/<sha256>/`).
  `trust_root` names the signed Sigstore trust bundle for keyless signatures.
  APK packages (Wolfi, Chainguard, Alpine) come from a `kind: apk` source: their
  provenance is the signed `APKINDEX.tar.gz` in `<data_root>/holger-provenance/apk/<source>/`,
  checked against the apk keys the same trust bundle pins (`apk-keys/<name>`); a gate may
  narrow them with `provenance: require(apk_keys: ["wolfi-signing.rsa.pub"])`.
- `allow_auto_approve: false`: nothing is approved automatically. A source's
  `auto_approve: when(...)` works only when the floor allows it, decides only the
  approval vote (`auto-approve:<source>`, never the importer), and counts as at most one
  of `approvals: 2`.

##### Read the real docs

- 🧭 **[Holger v2.0 — the plan: internet → upstream → USB → on-prem downstream](docs/HOLGER-V2.md)** — the products, what each does, the phases H1–H6, and what is built through the Build Thing.
- 🗒️ **[Features — `docs/FEATURES.md`](docs/FEATURES.md)** — what is new in Holger, with status, date, source path and commit.
- 📖 **[The complete manual — `docs/manual.pdf`](docs/manual.pdf)** — every subsystem chapter (server, agent, storage, auth, release & history).
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the detailed pitch, quickstart, design, the full competitive matrix, and all measured benchmarks.

##### Development setup

The generated docs in this repo are kept in sync by git hooks in `.githooks/`.
Point git at them **once per clone**:

```bash
git config core.hooksPath .githooks
```

On every commit that stages Rust source, a `Cargo.toml`, or
`.nornir/ai-context.toml`, the `pre-commit` hook regenerates the AI-context packs
(`docs/ai-context.md` + `docs/ai-context-full.md`) with
[kvasir](https://github.com/hentorp/kvasir) and stages them, so they never drift;
it also re-renders README/CHANGELOG via nornir. A missing tool warns and skips; a
kvasir validation error aborts the commit. kvasir is taken from `PATH` (install it
with `cargo install --path ../kvasir`) or a sibling `../kvasir` release build.

##### Contributing, security and changes

- [CONTRIBUTING.md](CONTRIBUTING.md): how to get the code, report a bug and propose a change.
- [SECURITY.md](SECURITY.md): how to report a vulnerability privately (holger@holger.rs).
- [CHANGELOG.md](CHANGELOG.md): what changed in each release, and the vulnerabilities each one fixed.

##### License

AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS (Rickard Ernst Björn Lundin) (jointly owned in equal
shares, 50/50). Commercial terms are available from both co-owners jointly; write to
gunnar@gunnar.rs. Versions published to crates.io on or before 2026-10-02 keep the
license they were published with (mostly MIT). See `LICENSE`, `NOTICE`,
`COMMERCIAL-LICENSING.md`, `TRADEMARKS.md` and `THIRD_PARTY_LICENSES.md`.


## nordisk/holger-egui

At https://gunnar.rs/nordisk/holger-egui.

This repository has no README.

## nordisk/holger-ui

At https://gunnar.rs/nordisk/holger-ui.

### README.md

#### holger-ui — web operator console for holger

Leptos/WASM single-page console for [holger](https://git.gunnar.rs/nordisk/holger),
talking **gRPC-web** straight to the server's tonic-web door. No REST layer, no
BFF — `proto/holger.proto` (+ `proto/holger-room.proto`) is the entire
contract. Login is holger's own passkey door (`/-/auth/passkey/*`); the
browser carries holger's session as a bearer and holger decides every call.

> Crate name is `holger-ui-web` (the holger main repo's egui app was once the crate
> `holger-ui`, now `holger-server-ui`); the repo keeps the short name.

##### Architecture

```
browser (Leptos/WASM)
  └─ tonic-web-wasm-client (fetch, gRPC-web)
       └─ holger server — tonic-web layer over the same gRPC services
            AdminService · RepositoryService · SearchService · …
```

- `build.rs` — generates client stubs at build time (client-only, no
  transport — the browser transport is `tonic-web-wasm-client`) with protox,
  pure Rust, no `protoc`. It compiles against **holger's own**
  `../holger/proto/holger.proto` whenever holger is checked out beside this repo.
- `proto/holger.proto` — the fallback copy for a standalone checkout only.
  `tests/proto_is_holgers.rs` fails when it differs from holger's; refresh with
  `cp ../holger/proto/holger.proto proto/`.
- `src/grpc.rs` — the one place that knows the server URL, builds clients, and
  attaches the bearer credential.
- `src/app.rs` — the console shell and views, routed by `leptos_router`.
- `assets/login.html` + `login.js` — the passkey page, served at `/login`:
  holger's ceremony, holger's session kept for the tab.
- `crates/holger-ui-server` — the thin server: the bundle, and a same-origin
  pass-through (`/grpc/*` → holger's gRPC door, `/-/*` and `/` → holger's HTTP
  door). No users, permissions or store. holger's estate front links it.

##### Routes — every view is a shareable URL

```
/                                   repositories
/repo/:name                         browse one repository
/artifact?repo=&ns=&name=&version=  artifact detail (query-addressed —
                                    raw artifact names contain slashes)
/search?name=&namespace=&version=&path=&checksum=
                                    search; a shared URL auto-runs
/replicate                          replication (preview-first)
/login                              token paste + OIDC/PKCE login
/marketplace/landing?subscription=&plan=&status=&http=&grpc=&client=&tenant=
                                    post-purchase hand-over from holger-server's
                                    marketplace door (or ?error=holger-marketplace-<n>)
```

A directly-opened URL fetches everything it needs from its address. Serving
rule: the host must fall back to `index.html` for unknown paths — Trunk's dev
server already does; give a production reverse proxy the same rule.

Version note: this repo pins tonic 0.14 (codegen only); holger's server is on
tonic 0.13. The gRPC-web wire protocol is independent of either side's tonic
version, so the skew is deliberate and harmless.

##### Prerequisites

```bash
rustup target add wasm32-unknown-unknown
cargo install trunk
```

##### Dev loop

```bash
trunk serve            # http://127.0.0.1:8090
```

`Trunk.toml` proxies `/holger.v1.*` to `http://127.0.0.1:50051`, so run a local
holger with its gRPC-web door on 50051 and the console is live — same-origin,
no CORS setup.

**Server-side prerequisite:** holger must expose a tonic-web layer on its gRPC
endpoint. That work lives in the holger main repo (tonic 0.13 ⇒ `tonic-web
0.13`, wrapped around the existing tonic router); until it lands, the console
renders with the health badge showing `unreachable`.

Override the target for a non-proxied build:

```bash
HOLGER_GRPC_WEB_URL=https://holger.example.com trunk build --release
```

##### Authentication

Two login forms, one credential store (sessionStorage; dies with the tab,
dropped by Log out). Whatever lands there rides every gRPC-web call as
`authorization: Bearer` via one tonic interceptor — roles stay the server's
RBAC mapping, the console adds no client-side gating.

- **Paste a token** — a static API token (`StaticToken` method) or an
  IdP-issued access token.
- **OIDC with PKCE** — authorization-code flow, public client, S256. Configure
  issuer + client id in the login view (persisted in localStorage) or prefill
  at build time with `HOLGER_OIDC_ISSUER` / `HOLGER_OIDC_CLIENT_ID`. The IdP
  must register the console origin as redirect URI and allow it CORS on the
  token endpoint (standard SPA registration). The scope is `openid`; the
  marketplace route below asks for more. The server validates the token
  through its configured `Oidc` method (JWKS offline or userinfo online).

**Marketplace hand-over.** When holger is bought through a cloud marketplace,
holger-server's `/-/marketplace/landing` door signs the buyer in with Entra
(the marketplace contract requires sign-in *before* activation), exchanges the
purchase token server-side (resolve · provision · activate) and 303s the
buyer's browser to `/marketplace/landing` here, carrying only the outcome in
the query — subscription id, plan, status, the tenant's HTTP and gRPC
addresses, the SSO app's client id (`client=`) and the buyer's Entra directory
id (`tenant=`) — or `?error=holger-marketplace-<n>` when it refused (the
server's codes are frozen, 1–14; each has a sentence in
`util::marketplace_error_sentence`, guarded by a native test). Neither the
purchase token nor the fulfilment client secret ever reaches the browser: the
token is single-use and consumed by the server, and the secret is read from
the server's environment, which is why this CSR-only console cannot be where
the exchange happens. That sign-in at the door is what made the buyer the
tenant's first Admin (keyed on its OIDC `sub`); signing in on the landing
route is the same identity, now for the console.

The route shows what was provisioned, prints the cargo/pip addresses, and
offers the same PKCE sign-in as above with the issuer derived from the
directory (`https://login.microsoftonline.com/<tenant>/v2.0`, the one issuer
the provisioned tenant trusts) and the client id preferring `client=` from the
hand-over over the saved / `HOLGER_OIDC_CLIENT_ID` value (persisted on
sign-in, so the Account page then shows the same). The scope it asks for is
`openid profile email <client_id>/.default`, not the plain `openid` of the
Account page: against Entra v2, `openid` alone yields an access token for
Microsoft Graph (`aud` = Graph), which the tenant — checking `aud` against the
SSO app's client id — refuses; `<client_id>/.default` is Entra's form for "an
access token for this app registration". For that to resolve, the publisher's
app registration must **expose an API** (an Application ID URI and at least
one scope under *Expose an API*) and issue **v2 access tokens**
(`requestedAccessTokenVersion: 2` in the manifest), so the token's `aud` is the
bare client id and its `iss` the v2 issuer the tenant pins — a v1 token
carries `aud: api://<client_id>` and `iss: sts.windows.net` and fails both
checks. Once signed in the route shows the `WhoAmI` subject and role: the
buyer's is already mapped; a colleague's is what an operator adds to the
tenant's `roles` map for a further admin, since an Entra subject is per user
*and* per app and cannot be written down before that first login. Deploy
note: the Entra origin must be in the CSP
(`IDP_ORIGIN=https://login.microsoftonline.com cargo xtask publish`), and the
console origin must be a *single-page application* redirect URI on that app
registration.

##### Release build

```bash
cargo xtask build              # what the recipe declares — trunk build --release
cargo xtask build --list       # every declared build point, and its command line
chmod -R a+rX dist             # the chmod matters when a rootless container serves them
```

Two console bundles (A28 M13). The open one, `dist/` from `index.html`, renders no word
about money and carries no sign-up, welcome or order page. The metered one,
`dist-metered/` from `index-metered.html` (`cargo xtask build --variant
mvp-ui-web-metered`: `trunk build --release --features metered --dist dist-metered
index-metered.html`), is the one holger's paid front embeds.

`cargo xtask build` owns no build knowledge: the package, the profile and the
target all come from the ONE declaration in `crates/holger-ui-recipe`
(`.nornir/build-variants.toml` is that declaration EMITTED — describing the build
for the tools that discover a repo by that path, never read back as authority;
re-emit with `cargo run -p holger-ui-build-thing -- emit`). Today it declares one
row, `mvp-ui-web`, and drives it through trunk — trunk also runs wasm-bindgen,
wasm-opt and the `index.html` pipeline, and writes the content-hashed names the
deploy globs for, so a bare cargo build would produce a file no browser loads.

`trunk build --release` by hand still does the same thing; the verb exists so
that the build point is written down once, in Rust, instead of in three prose
passages that can drift.

###### The open product: `cargo install` (Holger v2.0 H5)

The server installs with plain cargo, and the Build Thing row `ui` (beside holger's OPEN
rows `upstream` and `downstream`; here in this repository's one delivery, MVP) builds
exactly the same binary:

```bash
cargo install --locked --path crates/holger-ui-server
```

The host target, the release profile, no feature. `tests/ui_row_equals_cargo_install.rs`
holds the row to this line and to its tree guard (a crate bound, no outbound client, nothing
sold, no public site). The console bundle itself is trunk's (above); `mvp-ui` is the same
server cross-built for musl as the unit `--install` writes.

##### Production deploy — nginx in rootless podman (quadlet)

`deploy/` holds the whole shape: [nginx.conf](deploy/nginx.conf) (SPA fallback,
`/grpc/` stripped and proxied to holger on 50051, immutable caching for the
hashed wasm/js, streaming 1 GiB uploads) and
[holger-console.container](deploy/holger-console.container) (the quadlet unit,
host networking, loopback :8480).

The served tree is a **published copy** under `~/.local/share/holger-console`,
never the repo's `dist/` — the dev loop (`trunk serve`) rewrites `dist/` on
every rebuild and would silently replace the release with a debug build.

`cargo xtask publish` is the ONLY supported publish path: it builds the release,
computes the sha256 of trunk's inline bootstrap and generates the nginx config
with a matching Content-Security-Policy, then restarts `holger-console` over
D-Bus. Copying `deploy/nginx.conf` by hand leaves the CSP marker as a comment —
the site then serves with NO CSP at all.

```bash
cp deploy/holger-console.container ~/.config/containers/systemd/   # once
systemctl --user daemon-reload                                     # once
loginctl enable-linger        # once — survives logout, starts at boot
cargo xtask publish           # every release; add IDP_ORIGIN=… for OIDC
cargo xtask verify-chain      # the whole prod chain, outside-in
```

The listener stays loopback HTTP by design: TLS and exposure are the outer
layer's decision. On a tailnet:

```bash
tailscale serve --bg http://127.0.0.1:8480
```

###### The server behind it

[deploy/holger-server.service](deploy/holger-server.service) runs holger-server
itself as a systemd user unit: release binary in `~/.local/bin`, RON config in
`~/.config/holger-console/server.ron` (no inline secrets — the bearer token is
a `chmod 600` file resolved via `token_file`), data under
`~/.local/share/holger-console/store`. The full prod chain is then three
persistent pieces: `holger-server.service` (gRPC-web on 50051) ←
`holger-console` (nginx quadlet, :8480) ← `tailscale serve` (HTTPS on the
tailnet), all surviving reboot once `loginctl enable-linger` is set.

##### License

AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS (Rickard Ernst Björn Lundin) (jointly owned in equal
shares, 50/50). Commercial terms are available from both co-owners jointly; write to
gunnar@gunnar.rs. Versions published to crates.io on or before 2026-10-02 keep the
license they were published with (mostly MIT). See `LICENSE`, `NOTICE`,
`COMMERCIAL-LICENSING.md`, `TRADEMARKS.md` and `THIRD_PARTY_LICENSES.md`.


## nordisk/knut

At https://gunnar.rs/nordisk/knut.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<p align="center"><img src="assets/generated_knut_iceberg.png" alt="knut — a knytpunkt over graphs" width="560"></p>

#### knut

> A pure-Rust **columnar ↔ graph fabric** — read/write Apache **GraphAr**, bulk-load
> **FalkorDB**, and serve **Cypher over Arrow Flight SQL**, with **Bifröst** streaming
> **Iceberg** tables into a live graph by CDC (Spark-Connect *or* Spark-free). One Arrow
> pipe from the lakehouse to a queryable graph.

⚡ part of the **nordisk** constellation

##### Mashup — one Arrow pipe: columnar lake → live graph

knut carries graph data the whole way from a columnar lake to a live, queryable graph in
**one Arrow pipe** (no JVM required) — the rows marked **NA** are categories the rivals have
no concept for:

| capability | **knut** | GraphAr (official) | Neo4j / GDS | Spark + GraphFrames |
|---|:--:|:--:|:--:|:--:|
| Pure-**Rust** GraphAr — 5 formats (Parquet/CSV/ORC/JSON/Arrow), local **or** object store | **✅** | ◐ (C++/Java/Py) | ✗ | ◐ |
| **Cypher over Arrow Flight SQL** server — ADBC / Power BI / DataFusion clients, Arrow out | **✅** | **NA** | ◐ | ✗ |
| **Iceberg → graph CDC** fabric — snapshot-incremental + changelog (`_change_type` → MERGE / DETACH DELETE) | **✅** | **NA** | ✗ | ◐ |
| **Spark-free** Iceberg ingestion (skade `IcebergSource`, no JVM) | **✅** | **NA** | ✗ | ✗ |
| Bulk columnar `RecordBatch` → **live graph DB** (FalkorDB UNWIND/CREATE) | **✅** | ◐ | ✅ | ✅ |
| Mature graph-algorithm library + production graph engine | ◐ | ✗ | **✅** | **✅** |

*Rows marked **NA**: the rival has no such category. The measured numbers are below.*

##### Benchmarks

<!-- nornir:gen:start:bench_status -->
**v0.1.3 · oden · 32 cores · 2026-07-23**

| | |
|---|---|
| host | `oden` · 32 cores |
| version | `0.1.3` |
| measured | 2026-07-23 |
| arms | 29 |
| borrowed arms | **10** named `nornir.*` — measured here but owned by `nornir` |
| tests | 29/29 passed |
| publishable | yes |
| flagged implausible | **2** |

⚠ **2 arms carrying a number that is not credible as real work.** Each is marked ⚠ or withheld wherever it appears below — never published as a measurement:

| arm | what is not credible |
|---|---|
| `bifrost_fabric` | `rows_per_s` = 6.698e9/s derived from a 1.49e-5 s interval — under the 1e-4 s timing floor, so the rate is arithmetic, not measurement |
| `merge_property_groups` | `rows_per_s` = 1.067e10/s derived from a 9.37e-6 s interval — under the 1e-4 s timing floor, so the rate is arithmetic, not measurement |

ℹ **10 arms in this run belong to another repo** (10 × `nornir.*`). They are the harness's own suite, pulled in because the bench runner this repo pins predates the arm filter that would have excluded them — so they were measured here without being asked for. They describe `nornir`, not `knut`, and are shown separately below rather than mixed into this repo's numbers.
<!-- nornir:gen:end:bench_status -->

###### The Arrow pipe — knut's own arms

Storage formats round-tripped through the GraphAr writer/reader, the Bifröst CDC
fabric, the FalkorDB bulk path, and the Thund streaming window operators.

<!-- nornir:gen:start:benches bold=false cols=rows_per_s,read_rows_per_s,write_rows_per_s,incremental_rows_per_s,recompute_rows_per_s,checkpointed_rows_per_s,restore_resume_rows_per_s,speedup_x,telem_cores_busy_avg,telem_elapsed_ms exclude=nornir. suspect=exclude -->
**v0.1.3 · oden · 32 cores · 2026-07-23**

| workload | rows_per_s | read_rows_per_s | write_rows_per_s | incremental_rows_per_s | recompute_rows_per_s | checkpointed_rows_per_s | restore_resume_rows_per_s | speedup_x | telem_cores_busy_avg | telem_elapsed_ms |
|---|---|---|---|---|---|---|---|---|---|---|
| builder_dump | 1.42M | | | | | | | | 1 | 71.43 |
| cypher_render | 3.21M | | | | | | | | 1 | 38.22 |
| doput_dispatch | | | | | | | | 0.85 | 1.60 | 4766 |
| falkor_parallel_dispatch | | | | | | | | 29.75 | 1 | 801.77 |
| file_source_scan | 48.55M | | | | | | | | 1 | 20.62 |
| format_arrow | | 170.32M | 77.14M | | | | | | 1 | 10.89 |
| format_csv | | 8.18M | 5.44M | | | | | | 2 | 42.19 |
| format_json | | 1.08M | 8.44M | | | | | | 2 | 113.17 |
| format_orc | | 63.84M | 47.38M | | | | | | 1 | 12.46 |
| format_parquet | | 45.57M | 7.16M | | | | | | 2 | 24.88 |
| graphar_vertex_load | 35.85M | | | | | | | | 2 | 54.49 |
| ingest_lifecycle | 36.05M | | | | | | | | 2 | 24.69 |
| thund_checkpoint | | | | | | 14.11M | | | 2 | 157.51 |
| thund_incremental_agg | | | | 6.13M | 1.80M | | | | 1.50 | 1467 |
| thund_recovery | | | | | | | 33.59M | | 1 | 129.08 |
| thund_watermark_drop | | | | 2.22M | 3.81M | | | | 1 | 36.46 |
| thund_window_close | | | | 16.58M | 31.40M | | | | 1 | 193.79 |

⚠ **withheld from this table — not credible as real work:**

- `bifrost_fabric` — `rows_per_s` = 6.698e9/s derived from a 1.49e-5 s interval — under the 1e-4 s timing floor, so the rate is arithmetic, not measurement
- `merge_property_groups` — `rows_per_s` = 1.067e10/s derived from a 9.37e-6 s interval — under the 1e-4 s timing floor, so the rate is arithmetic, not measurement
<!-- nornir:gen:end:benches -->

<sub>`telem_cores_busy_avg` is the average number of CPU cores busy while the arm ran —
a single-digit value on a 32-core host means the arm is serial by construction, not that
the box was idle. Blank cells are metrics that arm does not report.</sub>

###### Borrowed arms — nornir's own suite, measured here by accident

<!-- nornir:gen:start:benches bold=false cols=elapsed_ms,rows_per_sec,queries_per_sec,symbols_per_sec,events_per_sec,speedup_vs_full,telem_cores_busy_avg name=nornir. suspect=exclude -->
**v0.1.3 · oden · 32 cores · 2026-07-23**

| workload | elapsed_ms | rows_per_sec | queries_per_sec | symbols_per_sec | events_per_sec | speedup_vs_full | telem_cores_busy_avg |
|---|---|---|---|---|---|---|---|
| nornir.delta_rescan | 8.19 | | | | | 2.70 | 0 |
| nornir.dep_graph_build | 24.07 | | 365085 | | | | 1 |
| nornir.dep_graph_build_ext | 31.88 | | 344637 | | | | 1 |
| nornir.funnel_append | 1284 | | | | 49.84 | | 1 |
| nornir.funnel_append_batch | 1.23 | | | | 52137 | | 0 |
| nornir.knowledge_query | 16.85 | | 23742 | | | | 1 |
| nornir.knowledge_scan | 1.78 | | | 674582 | | | 0 |
| nornir.plan_dag_assemble | 107.38 | | | | | | 0 |
| nornir.symbol_append | 4.59 | 239618 | | | | | 0 |
| nornir.warehouse_query | 1.22 | 410561 | | | | | 0 |
<!-- nornir:gen:end:benches -->

<sub>These arms measure **nornir**, not knut. `knut/xtask` pins `nornir = "0.5"` from
crates.io, and that published runner predates the `NORNIR_BENCH_ONLY` arm filter — so the
filter was accepted and silently ignored, and nornir's suite ran alongside knut's. They
are kept in their own table so they can never be read as knut's numbers; the fix is a
newer runner, not an edit to this file.</sub>

##### SQL for Power BI lives next door (2026-09-09)

knut's Flight SQL server (`graphar-flight`) speaks **Cypher** — the graph. Power BI, the
Arrow Flight SQL ODBC driver and ADBC speak **SQL**, and that dialect is
[**skade-flight**](https://git.gunnar.rs/nordisk/skade) in the skade
repo: DataFusion over the Iceberg warehouse, streamed Arrow, ODBC escapes translated, 129 Power Query
statement shapes green. Two dialects, two services, one wire; korp mounts both. The knut bridge —
graph tables as DataFusion table providers in that same session — is the next step, not done.

##### Read the real docs

- 📖 **[The complete manual — `docs/manual.pdf`](docs/manual.pdf)** — every subsystem chapter (graphar, graphar-falkordb, graphar-flight, Bifröst, pipelines) plus the test & bench matrix.
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the detailed pitch, architecture, quickstart, per-crate reference, and the full test matrix.
- 📝 [`docs/book.md`](docs/book.md) — the manual, as Markdown. Search it with `nornir docs search knut "<query>"`.


## nordisk/korp-collectors

At https://gunnar.rs/nordisk/korp-collectors.

### README.md

#### korp-collectors

**The standalone LEAF of korp's open-data collector layer.** One object-safe
`Collector` trait + per-access-METHOD import collectors (Swedish + EU open data),
parsing pure-Rust → Apache Arrow → (optionally) Apache Iceberg via **skade**.
Plus a `RegionSource` seam that FETCHES OSM extracts on demand.

This crate depends on **neither `korp` nor `facett`**, so both can consume it —
which breaks the `facett ⇄ korp` dependency cycle. (Mirrors how
`znippy-zoomies/osm-katana` and `Skidbladnir` were extracted as leaves.) The
ravens **Hugin & Munin collect** — hence "collectors" (née connectors).

##### Collectors (all `Direction::Source`)

| name | kind | parses | feature |
|------|------|--------|---------|
| `dcat-ap-se` | DCAT-AP-SE crawl | RDF/XML + SPARQL-JSON → dataset/distribution registry | `parsers` |
| `pxweb-jsonstat2` | PxWeb / JSON-stat2 | statistical cube → code-col-per-dim + `value` | `parsers` |
| `eurostat-sdmx`, `oecd-sdmx`, `ecb-sdmx`, `insee-sdmx`, `ons-sdmx`, `istat-sdmx`, `worldbank-sdmx` | SDMX-JSON 2.1 / 1.0 | **generalised** series/observation dim split → per-`(dims × time × measure)` value rows — one collector, any SDMX provider (Eurostat/OECD/ECB/IMF/**INSEE** France/**ONS** UK/**ISTAT** Italy/**World Bank**/EEA), zero new parsing — presets differ only in branding + endpoint | `parsers` |
| `entsoe`, `entsoe-generation`, `entsoe_day_ahead`, `nordpool`, `energy-charts-power` | ENERGY market | ENTSO-E market-document XML → time-series points (SE1–SE4 ⇄ EIC): `entsoe` load/generic `quantity`, `entsoe-generation` per-`psrType` volume (A75), **`entsoe_day_ahead`** day-ahead SPOT PRICES (A44, `price.amount` per zone × hour → `entsoe_day_ahead_prices`; the open free-token price route, token via `ENTSOE_TOKEN`); Nord Pool day-ahead zones (licensed twin), Fraunhofer generation-mix | `parsers` |
| `opensky-flights`, `opensky-europe-bbox`, `opensky-tracks` | OPEN FLIGHT recorder (ADS-B) | OpenSky `states/all` positional-array → flight position table → `osm3d-flight` scene (**live auth: OAuth2 client-credentials**, 2025 Keycloak, via `OPENSKY_CLIENT_ID`/`OPENSKY_CLIENT_SECRET`). **`opensky-tracks`** = the recorded flight PATH: `/tracks` `path[]` → ordered `(time, lat, lon, baro_altitude, heading, on_ground)` waypoint trajectory → `opensky_tracks` table → `osm3d-flight` replay (basic auth via `OPENSKY_USER`/`OPENSKY_PASS`) | `parsers` |
| `smhi-hydroobs` | WATER | SMHI hydrological observations (river discharge / water level) — station `value` series → Arrow | `parsers` |
| `metno-forecast` | WEATHER | Met.no Locationforecast point forecast → per-time forecast table (temp, wind, humidity, pressure, cloud) → `osm3d` scene | `parsers` |
| `gbif-occurrences` | biodiversity | GBIF occurrence search (keyless, CC0/CC-BY) → occurrence point table (taxon + WGS84 fix + country) → `osm3d-poi` scene | `parsers` |
| `worldbank-indicators`, `restcountries` | reference / stats | World Bank Open Data (`[meta,data]` envelope) → per-`(country, year, indicator)`; RESTCountries → ISO-3166 country join spine | `parsers` |
| `mobility-database`, `mobility-database-rt` | transit META-catalogue | Mobility Database (CC0, the **TransitFeeds successor**) → typed feed-descriptor rows that **auto-drive** the GTFS collectors; bearer-token via `MOBILITY_DB_TOKEN`. `mobility-database` enumerates STATIC GTFS feeds (`/v1/gtfs_feeds`); `mobility-database-rt` enumerates **GTFS-Realtime** feeds (`/v1/gtfs_rt_feeds`, id, `entity_type` vp/tu/sa, provider, country, `.pb` URL) that drive the `gtfs-rt-vehicles` protobuf reader for live vehicle positions | `parsers` |
| `erddap-emodnet-physics`, `erddap-noaa-coastwatch` | ERDDAP scientific data | ERDDAP `.json` `table` (columnNames + columnTypes + rows matrix) → typed Arrow, uniform over any dataset id on any ERDDAP server (EMODnet ocean + global ocean/atmos); `.csv` variant via the `csv` collector | `parsers` |
| `eea-wise-water`, `destatis-genesis`, `cbs-odata` | EU open data | EEA WISE water quality (Waterbase via the Discodata SQL API, `/results`) → per-`(site, determinand, year)` rows; Destatis GENESIS-Online table catalogue (`/List`, token via `DESTATIS_GENESIS_TOKEN`); CBS Open Data (Statistics Netherlands) OData TypedDataSet (`/value`) → typed observations | `parsers` |
| `polisen-events`, `kolada-kpi`, `smhi-metobs`, `smhi-weather`, `ais-positions`, `marinetraffic-ais`, `gbfs-citybikes`, `osm-overpass`, `bolagsverket-organisationer` | generic REST/JSON | config-driven JSON field-map → Arrow | `parsers` |
| `usgs-earthquakes` | USGS GeoJSON feed | `FeatureCollection` Point features → quake rows (id, place, magnitude, depth, lon/lat, epoch-ms time) → `osm3d` scene | `parsers` |
| `rss-atom` | RSS/Atom feed | RSS 2.0 / Atom 1.0 syndication (dialect-agnostic `quick-xml`) → per-item rows (feed_title, id, title, link, summary, author, published, categories JSON) | `parsers` |
| `openaq-air-quality` | OpenAQ REST | air-quality `results[]` JSON → measurement rows (station, parameter, value, unit, lon/lat, country, city, UTC time; nested `coordinates`+`date.utc` flatten) → `osm3d` scene | `parsers` |
| `energy-charts-power` | Energy-Charts REST | generation-mix JSON — parallel `unix_seconds[]` axis ZIPPED against each `production_types[].data[]` by index → per-`(time, type)` power rows (Europe-wide via `?country=`; CC-BY) | `parsers` |
| `wfs` | OGC WFS | `GetFeature` GML `FeatureCollection` → feature rows (WKT geometry + properties JSON) → `osm3d` scene | `parsers` |
| `gtfs-rt-vehicles` | GTFS-Realtime feed | `VehiclePositions` **protobuf** `FeedMessage` → live transit vehicle position table (entity/vehicle/route/trip id, lat/lon, bearing, speed, timestamp, stop) via a hand-rolled protobuf reader (no `prost` dep) → `osm3d-flight` scene | `parsers` |
| `trafikverket-trainpos` | Trafikverket | POST XML `<REQUEST>` → JSON position table | `parsers` |
| `osm-geoparquet`, `osm-admin-boundaries` | OSM bulk file | PBF → WKB ways / admin MultiPolygons (via `osm-katana`) | `osm` |
| `csv` | flat file | RFC 4180 (+ `;`/TAB, BOM, Latin-1, decimal-comma) → typed Arrow via a two-pass schema sniff | `csv` |
| `mainframe-copybook` | flat file | IBM EBCDIC fixed-width decoded against a COBOL copybook (PIC X/9/S9V, COMP-3 packed, zoned, COMP binary) → Arrow (COMP-3 → `Decimal128`) | `mainframe` |
| `fixed-width` | flat file | ASCII/Latin-1 fixed-width decoded against a COBOL copybook layout (DISPLAY usage) | `mainframe` |
| `copernicus-cds`, `dwd-icon` | NetCDF/GRIB weather model | hand-rolled pure-Rust GRIB2 + NetCDF-classic decode → a variable/time/bbox **metadata catalog** (discipline/param, reference time, Ni/Nj, lat/lon bbox, num_points) **AND the data-ARRAY unpack**: GRIB2 Section 5/6/7 simple (5.0) + complex (5.2) + complex-with-spatial-differencing (5.3) packing with Section-6 bitmaps, NetCDF-classic variable arrays (fixed + record dims). `params.mode="values"` emits per-point grid values. Copernicus CDS (`retrieve/v1`, key from `CDSAPI_KEY`) + DWD ICON. Only NetCDF-4/HDF5 stays deferred | `gribnc` |
| `copernicus-era5`, `copernicus-cams` | Copernicus reanalysis (value-first) | ERA5 climate reanalysis (CDS `retrieve/v1`, key from `CDSAPI_KEY`) + CAMS atmosphere/air-quality (ADS `retrieve/v1`, key from `ADSAPI_KEY`) → **per-point grid VALUE rows** by default (reuses the `gribnc` GRIB2 Section 5/6/7 + NetCDF value unpack, `mode="values"`) | `gribnc` |

Everything parses in pure Rust → Arrow `RecordBatch`. The memory `BatchSink`
buffers batches for assertion; the `skade-sink` feature adds the Iceberg backend
+ the path-agnostic `run()` runner.

##### The `RegionSource` seam (feature `osm`)

```rust
pub trait RegionSource: Send {
    /// Ensure the OSM source data for a named region is available locally,
    /// FETCHING it if missing. Returns the local .osm.pbf path.
    fn ensure(&self, region: &str) -> Result<std::path::PathBuf, String>;
    fn name(&self) -> &str;
}
```

* `CachedLocalSource { dir }` — resolves an already-present
  `{region}-latest.osm.pbf` (no fetch).
* `HttpOsmSource { base_url, cache_dir }` — the REAL connector: downloads
  `{base_url}/{region}-latest.osm.pbf` (Geofabrik-style) into `cache_dir`,
  skipping the download when already cached.

This is what lets a facett region picker be "fed by a connector fetching data"
without facett depending on korp.

##### Features

* `parsers` (default) — trait/registry surface + shared HTTP core + all light
  pure-Rust parsers + the pure OSM admin tag-filter/trie. No heavy geo dep.
* `osm` — the OSM geometry collectors (via `osm-katana`) + the `RegionSource` seam.
* `csv` — the RFC-4180 (+ dialects) CSV collector (`csv` 1.4 + `encoding_rs`).
* `mainframe` — the EBCDIC + COBOL-copybook + COMP-3 fixed-width collector and
  its ASCII fixed-width cousin (fully hand-rolled, no new deps).
* `gribnc` — the NetCDF/GRIB weather-model collector (Copernicus CDS + DWD ICON):
  hand-rolled pure-Rust GRIB2 + NetCDF-classic decoders (no new deps) → a
  variable/time/bbox metadata catalog **and the data-array unpack**. GRIB2 Section
  5/6/7 decodes simple (5.0), complex (5.2) and complex-with-spatial-differencing
  (5.3) packing with Section-6 bitmaps (a 0-bit is a missing point, never a fake
  0.0); NetCDF-classic reads variable data arrays (fixed + record dims, numeric
  types). Only NetCDF-4/HDF5 stays deferred behind a would-be optional, non-default
  native `netcdf` (libnetcdf C) feature.
* `skade-sink` — `BatchSink::iceberg` + the `run()` Iceberg runner (skade 0.5).
* `znippy` — the **collected-data converter** (`znippy_pack`): pack parsed Arrow
  batches (or raw byte blobs) into a **znippy** archive — compact, blake3-checksummed,
  OpenZL-compressed storage/transport — and read them back **byte-identical**.
  Reuses the `znippy-common` pack format (no bespoke compression) + `arrow-ipc`.
  `pack_tables`/`unpack_tables` for named `RecordBatch` streams; `pack_blobs`/
  `unpack_blob` for raw payloads.
* `daemon` — the collector **daemon** architecture (`daemon.rs` + `iceberg_daemons.rs`):
  the object-safe Config + State + Collector trio, the `CollectorCtx` seams
  (`WarehouseSink`, the GET/POST `Fetcher`, the `SecretStore` (API keys via an injectable seam — env default, vault later), the jera phase handle, the BIOS bus) and the
  `Offline → Downloading → Indexing → Online` state machine (empty-is-RED → `Failed`). Each
  registered daemon self-registers its live phase + facet into `collector-register.redb`
  (`register.rs`), so a `RegisterReader` in ANOTHER process — korp's Collectors panel, or
  `korp-collectors status` — sees it. `daemon_registry()` lists 21 warehouse-sink daemons
  under this feature (+ `gtfs-stops` under `gtfs`, + `osm-cut` under `osm-daemon`), among
  them the Swedish real-time trio **`polisen-events`** and **`smhi-weather`** (GET JSON) and
  **`trafikverket-trainpos`** (a POST XML `<REQUEST>` with its key in the body). Pure
  `parsers` — no korp, no facett; a daemon runs standalone (produces Arrow to the warehouse
  with no korp server) and, when korp is present, is visible through the shared register.
* `service` / `systemd` — run a daemon (or a whole nursery of them) as its own
  supervised process / systemd unit (`collector-<name>.service`), via Skidbladnir; the
  `korp-collectors` binary is the runner (`serve` / `status` / `run` / `install`).
* `testmatrix` — the tests' `functional_status` self-reports become real nornir
  test-matrix rows. Compiled OUT of the default build (no nornir dep otherwise).

##### Test

```
cargo test                                 # parsers (offline inject/assert)
cargo test --features osm                  # + OSM ways + RegionSource fetch/cache/parse
cargo test --features znippy               # + znippy converter byte-identical round-trip
cargo test --all-features                  # + skade Iceberg round-trip + testmatrix
```

The `RegionSource` test spins an in-process HTTP server serving a bundled
454-byte `.osm.pbf` fixture, proves fetch → cache-hit (a request counter) →
`osm-katana` parses ≥1 real way. Deterministic, no external network.

##### License

MIT OR Apache-2.0.


## nordisk/monetize

At https://gunnar.rs/nordisk/monetize.

### README.md

<img src="assets/monetize.webp" alt="monetize" width="180" align="right" />

#### monetize

The money engine for the Vetra products: plans, entitlement as signed facts, the
reserve → provision → capture transaction, plugin seams for payment vendors and
cloud providers, and paid signup. One gRPC server and one operator console serve
many products; a product compiles in a thin client and puts one check at its own
choke point.

* Plugin seams for payment vendors and cloud providers. The paid implementations live in a separate private repository; this tree carries the traits, the engine, the server, the console, and the implementations nobody needs an account for.
* Signed entitlement facts a product verifies offline: one atomic load and a map lookup, no network call on the request path.
* Operator console (Leptos/WebAssembly), served by its own read-only backend.
* An append-only ledger written before every external call; every derived number is a fold of it.
* The same code runs https://monetize.vetra.se

##### Building

```
git clone ssh://git.gunnar.rs:2222/nordisk/monetize
git clone ssh://git.gunnar.rs:2222/nordisk/edda
git clone ssh://git.gunnar.rs:2222/nordisk/Skidbladnir
cd monetize
cargo build --release -p monetize-server -p monetize-ui-server
```

The root workspace takes some crates by path from the sibling checkouts `../edda` and
`../Skidbladnir`, so both sit beside the clone: a clone of monetize alone does not
build. A product that links monetize takes its open crates (`monetize-embed`,
`monetize-product`, `monetize-proto`, ...) from crates.io and needs no checkout.

This page documents **monetize 0.3.0** — the version `monetize-server --version`
prints.

Docs: [Integrating an application with monetize v2.0](docs/INTEGRATION-v2.md)
([the picture](docs/integration-v2.svg), [the data model](docs/datamodel-v2.svg)) · [today's STE manual](docs/integration-guide.md)
· the plan: `MONETIZE-V2.0-PLAN.md` in workspace_nordisk.

License: AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS. Commercial
terms: `COMMERCIAL-LICENSING.md`.

##### The solo monetize box

ONE machine runs monetize for every product. `monetize-admin` (its own
workspace, `crates/monetize-admin`) is that box's PID 1 on a tunnr kernel, and
the same binary is its two other processes, each on its own loopback address:

| process | started as | listens on |
|---|---|---|
| monetize-server | `monetize-admin server --config /data/monetize/monetize.toml` | `127.0.0.1:50061` |
| the console | `monetize-admin ui --bind 127.0.0.1:8091 --server 127.0.0.1:50061 --root /data` | `127.0.0.1:8091` |
| PID 1 (the edge) | korp-start, no arguments | `:80`/`:443`, the operator's door `:8475` |

The edge carries `/callback/<kind>/<instance>` and the estate-facing gRPC
services (`SoldEstate`, `Signup`, `EstateOrder`, `Purchase`, `Products`) to the
server, refuses every other gRPC service (404), and carries the rest to the
console. monetize dials no estate: every estate calls monetize. Its only
outbound calls go to the cloud providers and the payment vendors.

Each product names its vendor instance. Each Stripe instance reads
`MONETIZE_STRIPE_<INSTANCE>_SECRET_KEY` and
`MONETIZE_STRIPE_<INSTANCE>_WEBHOOK_SECRET`, and its webhook is
`https://<box>/callback/stripe/<instance>`:

```toml
[vendors.stripe.gunnar]
enabled = true
[vendors.stripe.holger]
enabled = true

[products.gunnar]
vendor = "stripe/gunnar"
[products.holger]
vendor = "stripe/holger"
```

Each product sells its own signup from its own `[signups.<product>]`: its
plans (every id starts with `<product>/`), the meter their caps land on, its
oversell allowance and its signup guest (the feed's `signup_guest.<product>.*`).
The block's name is the product; `Signup.CheckName`, `Signup.Start` and
`Signup.Plans` name the product they are for, and a call that names none is
refused. The old single `[signup]` is refused at load.

```toml
[signups.gunnar.plans."gunnar/10gb-1m"]
amount_minor = 10000
currency = "Sek"
cap_bytes = 10737418240
period_days = 30
[signups.holger]
cap_meter = "disk_bytes"
```

The installer feeds the box (the kit's declaration), and after the install the
operator PUTs the whole feed on the door (`PUT /feed`, mutual TLS, the
operator's certificate only). A feed monetize-server would not start on (for
example a priced product with no `vendor`) is refused whole, by name.

##### The gRPC services

gRPC is the only control plane. The one exception is the vendor webhook, which is
HTTP because the vendor speaks HTTP.

| service | what it answers |
|---|---|
| `Products` | which product plugins are linked, the meters each declares and whether a cap on one is policed, the deployment's price lists and its one policy, each product's reachability, and the spend against each cloud's ceiling |
| `Purchase` | a quote for an order, the start of the reserve → provision → capture transaction, the state of an attempt, and the abandonment of one |
| `Usage` | one tenant's meters, every tenant's at once, a meter's history, its trend and how long there is, and the fleet total against what the iron can serve |
| `Ledger` | the append-only rows for a reference or a tenant, what is still open, a deterministic text export of the operational state, and attempts folded to one line each |
| `Entitlement` | what monetize believes about a tenant, signed: read one, list them, set one, a whole signed snapshot, the public key a product verifies with, reclaim of a lapsed grant, and what a tenant currently holds |
| `Signup` | the state of one self-service signup, polled by the return page a payment vendor sends the customer back to |
| `SignupAdmin` | every signup, including the refused ones — the operator's read, deliberately a second service so the return page cannot enumerate them |
| `Events` | one bounded stream of what happened, so the console stops polling; every stream opens with an epoch and a gap is reported rather than hidden |

##### Thanks

First to its inventors, Vetra Automation AB and Ignalina ApS (Danskjävlarna).

monetize uses excellent software from others: the neat redb (Christopher
Berner).

##### Sponsors

None so far — we would love to have one. Contact gunnar@gunnar.rs.


## nordisk/nornir

At https://gunnar.rs/nordisk/nornir.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<p align="center">
  <img src=".nornir/assets/nornir.webp" alt="Urðr, Verðandi, Skuld — the three Norns" width="720" />
</p>

#### nornir remembers every state your workspace has been in

**Time-travelling project-management for multi-crate Rust.**

- **Time-travel**: append-only, git-SHA-addressed warehouse — query any past state of code, tests, and releases
- **Unified**: code-intelligence, releases, tests, and a knowledge graph in one catalog — what Maven + Jira should have been
- **Gate**: a self-discovering completeness gate — the test-matrix knows what's unbuilt and untested
- **Embedded**: single-file ACID catalog (skade-katalog) — no Nessie/Polaris service to stand up
- **Fast**: 2.0M ops/s · 0.31 µs p50 catalog reads, lock-free per core

<!--
  A curated headline, not `mashup`: nornir's suite is now 154 arms and its arm ids are
  DOTTED (`nornir.mimir.search.…`). mashup infers rival "systems" from `_`-delimited
  prefixes, so on this suite it reads every arm as its own system and emits an
  87-column, 26-row sheet nobody can read. The arms below are the ones this page's
  bullets actually claim; the whole 154-arm set lives in the run itself.
-->
<!-- nornir:gen:start:benches bold=false cols=queries_per_sec,lookups_per_sec,rows_per_sec,p50_us,p99_us,elapsed_ms,speedup_vs_full,telem_cores_busy_avg compare=nornir.mimir.search.corpus_bytes,nornir.mimir.search.ragnar_as_of_100k,nornir.mimir.search.vec_hnsw_1k,nornir.knowledge_query,nornir.warehouse_query,nornir.delta_rescan,nornir.plan_dag_assemble -->
**v0.5.4 · oden · 32 cores · 2026-07-23**

| workload | queries_per_sec | lookups_per_sec | rows_per_sec | p50_us | p99_us | elapsed_ms | speedup_vs_full | telem_cores_busy_avg |
|---|---|---|---|---|---|---|---|---|
| nornir.mimir.search.corpus_bytes | 2.0334M | | | 0.32 | 0.36 | 0.06 | | 0 |
| nornir.mimir.search.ragnar_as_of_100k | | 13.05M | | 0.08 | 0.09 | 2.51 | | 1 |
| nornir.mimir.search.vec_hnsw_1k | 0.0126M | | | 79.10 | 85.02 | 20.34 | | 1 |
| nornir.knowledge_query | 0.0205M | | | | | 19.48 | | 1 |
| nornir.warehouse_query | | | 315139 | | | 1.59 | | 1 |
| nornir.delta_rescan | | | | | | 10.28 | 2.85 | 0 |
| nornir.plan_dag_assemble | | | | | | 103.46 | | 1 |
<!-- nornir:gen:end:benches -->

<!-- nornir:gen:start:bench_status -->
**v0.5.4 · oden · 32 cores · 2026-07-23**

| | |
|---|---|
| host | `oden` · 32 cores |
| version | `0.5.4` |
| measured | 2026-07-23 |
| arms | 154 |
| tests | 154/156 passed — **2 FAILING** |
| publishable | yes |

⚠ **2 tests failed in this run:**

- `nornir.mimir.search.vec_hnsw_100k` — HNSW recall@10 0.2656 fell below floor 0.9000 (rows=100000, dim=128)
- `nornir.mimir.search.vec_hnsw_10k` — HNSW recall@10 0.7320 fell below floor 0.9000 (rows=10000, dim=128)
<!-- nornir:gen:end:bench_status -->

<sub>Every table on this page is **rendered** from that run by `nornir docs render nornir`
— re-render it, never retype it. The host and core count are part of the stamp on purpose:
numbers from two different machines are two different measurements and must never be
merged into one trend line.</sub>

##### Read the real docs

- 📖 **[The complete manual — `docs/book.pdf`](docs/book.pdf)** — every subsystem chapter (design, warehouse, docs generation, vector search, release & history).
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the detailed pitch, quickstart, design, the full competitive matrix, and all measured benchmarks.
- 📝 [`docs/book.md`](docs/book.md) — the manual, as Markdown. Search it with `nornir docs search nornir "<query>"`.


## nordisk/skade

At https://gunnar.rs/nordisk/skade.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<p align="center"><img src="skade.webp" alt="Skade — the winter queen who keeps the icebergs in order" width="560"></p>

#### Skade

> Pure-Rust Iceberg. The catalog rests on a static search tree — lookups in
> nanoseconds — and it embeds: one file, opened in-process.
>
> On catalog reads it measures 492× Nessie and 677× Polaris. The benchmarks are below.

⚡ **GATLING** — all cores, no CPU wait · no copy · no allocation · no rayon · no Java, no Python

##### Mashup — an all-core data plane over an embedded catalog

Built to [Brokkr's software criteria](https://git.gunnar.rs/nordisk/dwarves).
Concurrency is [znippy-zoomies `gatling`](https://git.gunnar.rs/nordisk/znippy-zoomies).
SQL is [DataFusion](https://datafusion.apache.org/). The catalog index is
[Ragnar Groot Koerkamp's static search tree](https://curiouscoding.nl/posts/static-search-tree/) —
9 ns lookups, 40× binary search.

**Skade vs stock Iceberg vs raw Parquet — `oxr`, 1M rows, `oden` · 32 cores (tmpfs), 2026-07-24:**

| data plane | write (M rows/s) | read (M rows/s) | read cores busy |
|---|--:|--:|--:|
| **Skade** — gatling all-core encode + all-core scan | **44.4** | **234.1** | **9.5** / 32 |
| Skade — the *same* scan, **single core** | 42.6 | 51.2 | 1.0 / 32 |
| Iceberg — stock `iceberg-rust` writer + scan, same catalog | 10.0 | 37.0 | 1.0 / 32 |
| raw Parquet — `ArrowWriter`, no catalog / no snapshots (serial read) | 9.2 | 62.0 | 1.0 / 32 |

Skade writes **4.4× stock Iceberg** and **4.8× raw Parquet** on the same hardware. And it
reads on **all cores**: the ONE gatling engine fans the manifest parse, footer load, and
Parquet decode across the box, so 1M rows read single-core at 51.2 M/s (1 core) scale to
**234.1 M/s at 9.5 cores — 4.6×** — while a single-writer engine pins one core (Iceberg,
raw Parquet: 1.0/32).

Honest ceiling, measured not asserted: skade's scan tops out at **~9 busy cores on this
UNCOMPRESSED 4-column shape because it is DRAM-bandwidth bound, not fan-out bound** —
per-core throughput *halves* (51 → 25 M/s/core) as cores are added, and more work units
lift cores-busy but not total throughput. On **compute-bound** data (compressed,
high-entropy) the identical scan keeps **~21 of 32 cores at a 13× single→all-core speedup**
(`scan_core_saturation` test). A raw *row-group-parallel* Parquet read (no catalog) goes
faster still — 394 M/s at 12.7 cores — so skade's read gap over it is the catalog/snapshot
cost, not decode; the full per-core breakdown and that control arm are in
[the full overview](.nornir/README-full.md).

The catalog is one redb file, opened in-process. Nessie and Polaris are servers — a JVM,
a port, a process to keep alive:

| `table_exists` | Skade | vs Nessie | vs Polaris |
|---|--:|--:|--:|
| throughput | 2.0M ops/s | 492× | 677× |
| latency p50 | 0.31 µs | | |

*The full leaderboard — every storage variant, all four catalog capabilities, the head-to-head chart — is in [the full overview](.nornir/README-full.md) and the manual.*


##### Serve it: skade-flight, the SQL door (new 2026-09-09)

Power BI, the Arrow Flight SQL ODBC driver, ADBC and DataFusion clients speak SQL —
**[`skade-flight/`](skade-flight/)** is that door: Apache Arrow Flight SQL over the skade
warehouse, DataFusion in, streamed Arrow out. One long-lived session, a plan cache,
per-partition parallel encode, the ODBC dialect shim (`SELECT TOP n`, `{d …}`, `{fn UCASE(x)}` …),
and the column metadata the ODBC driver needs to fold filters. Measured on 2M rows × 6 columns:
35–40M rows/s over the wire, 87M projected, plan latency 40–80 µs. Two test matrices ship with it:
every Iceberg datatype × 8 checks, and 129 Power Query statement shapes (38 verbatim from
Microsoft's diagnostics), all emitting nornir testmatrix rows.

```sh
skade-flight --warehouse /var/lib/skade/warehouse --listen 0.0.0.0:50055 --token "$T" --basic pbi:secret
```

(`skade-katalog serve` remains the catalog door — REST / gRPC for Iceberg-aware engines; skade-flight
is the SQL door for BI tools. One of them per warehouse directory.)

##### Read the real docs

- 📖 **[The complete manual — `docs/manual.pdf`](docs/manual.pdf)** — the complete anatomy.
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — design, quickstart, and every measured benchmark.
- 📘 **[The idiot's guide — `docs/idiot_guide.pdf`](docs/idiot_guide.pdf)**


## nordisk/tunnr-mullvad

At https://gunnar.rs/nordisk/tunnr-mullvad.

This repository has no README.

## nordisk/tunnr-tailscale

At https://gunnar.rs/nordisk/tunnr-tailscale.

This repository has no README.

## nordisk/znippy

At https://gunnar.rs/nordisk/znippy.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

![znippy](assets/znippy.jpg)

#### znippy

> A parallel, random-access archive: pack a directory at all-core speed, pull any
> single file back without unpacking the rest — and query the index in
> **DuckDB / Polars / DataFusion**, because it's just Arrow IPC.

⚡ **GATLING** — one all-core engine · zero rayon · zero-copy · ~11/12 cores

##### Mashup — one archive vs tar/zip/7z/parquet

znippy is the only archive that's all-core on *both* ends, random-access per file,
**and** directly queryable — without pretending to beat zstd/7z on raw ratio:

| capability | **znippy** | tar + zstd | zip | 7z | parquet |
|---|:--:|:--:|:--:|:--:|:--:|
| All-core **compress** | **✅** | ~ (`-T0`) | ◐ | ✗ | ✅ |
| All-core **decompress** | **✅** | ✗ serial | ✗ | ✗ | ✅ |
| Random single-file read | **✅** | ✗ | ✅ | ✗ (solid) | — (columnar) |
| **Query without the tool** (DuckDB / Polars) | **✅** | ✗ | ✗ | ✗ | ✅ |
| Best pure **ratio** | ◐ | ✅ (`-19`) | ✗ | ✅ | — |

*The full capability matrix and every measured per-backend throughput table (JAR /
ZIP / gzip / bzip2 decode vs the legacy C tools) are in the full overview.*

###### Measured — znippy vs Arrow-IPC / zstd

One byte-identical mixed corpus (compressible text + incompressible blobs) packed
into a single compressed, queryable Arrow-IPC container two ways — znippy's
all-core pipeline vs a stock arrow-rs `FileWriter` with `CompressionType::ZSTD` —
then fully read back. Same box, same run, warmup + iterations:

<!--
  `benches name=mashup winner=row`, NOT a bare `mashup`. The prose above promises ONE
  head-to-head: `znippy_mashup` vs `arrowipc_mashup`, the two arms that pack the same
  corpus. A bare `mashup` auto-discovers "systems" from the leading `_`-delimited
  segment of every result name in the run, which here means it treats each of znippy's
  own ~20 arms (archive_…, footer_…, stree_…, subindex_…) as a separate rival system —
  it rendered a 67-column table with two populated cells per row. Filtering by name
  keeps this block the comparison the paragraph describes.
  See workspace_nordisk/nornir_agents_problems.md N2.
-->
<!-- nornir:gen:start:benches name=mashup winner=row -->
**v0.9.6 · oden · 32 cores · 2026-07-22**

| workload | compress_mbs | cores | decompress_mbs | files | in_mb | peak_rss_mb | ratio_x |
|---|---|---|---|---|---|---|---|
| arrowipc_mashup | **416.22** | 32 | 908.10 | 8000 | 1000 | 5208 | 1.44 |
| znippy_mashup | 111.89 | 32 | **8466** | 8000 | 1000 | 5208 | 1.47 |
<!-- nornir:gen:end:benches -->

*Not the same job: znippy stores every file as its own random-access, blake3-verified
chunk behind a queryable FST index and an immutable seal; the Arrow-IPC/zstd rival
concatenates ~1000 files per record-batch into one zstd stream — smaller granularity
traded away, no per-file addressing. So arrow's simpler batching wins raw
compress throughput while znippy wins decompress and keeps capabilities arrow can't
offer; both hit the same ratio on identical distinct data. Each side uses its
library's default codec settings.*

##### Read the real docs

- 📖 **[The complete manual — `docs/manual.pdf`](docs/manual.pdf)** — every subsystem chapter (format, Gatling pipeline, random-access, Iceberg/seal, decompressors).
- 📄 **[Full overview — `.nornir/README-full.md`](.nornir/README-full.md)** — the detailed pitch, quickstart, what's-new, the full competitive matrix, and all measured benchmarks.
- 📝 [`docs/book.md`](docs/book.md) — the manual, as Markdown.


## nordisk/znippy-zoomies

At https://gunnar.rs/nordisk/znippy-zoomies.

### README.md

<!-- ⚠ GENERATED by nornir from .nornir/README.md — DO NOT EDIT this file -->

<p align="center">
  <img src=".nornir/assets/znippys.jpeg" alt="znippy-zoomies" width="320" />
</p>

#### znippy-zoomies

Single-purpose parallel Rust primitives built on one all-core engine, not rayon's fork-join model.

- **`gatling`** — multicore streaming engine; zero copy, allocation, I/O-wait, and CPU-pause.
- **`lgz` / `lbunzip2`** — pure-Rust parallel gzip / bzip2 decoders on gatling.
- **`ldeflate`** — pure-Rust parallel DEFLATE/zlib **compressor**; gzip / ZIP / JAR on one planner.
- **`stree` / `stree32`** — AVX2 static search trees, ~40 ns/query.
- **`vtd`** — parallel OSM-XML scanner → compact byte-offset index; bounded RAM.
- **`psort`** — parallel sample sort over 16-byte `i64`-keyed records.

##### Decompressor benchmarks

Each of our parallel decoders against the standard tools **for its own format**, grouped by
format — every one of ours vs every one of theirs, in two modes. One deterministic 1 GB
corpus, min-of-3. Columns are hand-picked (MB/s · cores · wall s · peak RSS). A 🏆 marks a
decoder that beat a long-reigning record — see [`records.toml`](records.toml).

###### Multi core variants — all cores

**gzip**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-gz,pigz self=znippy-gz winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-gz | **5760** | 10.24 | **0.18** | 416.60 |
| pigz | 589.32 | 1.27 | 1.74 | 2.50 |
<!-- nornir:gen:end:benches -->

**bzip2**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-bz2,lbzip2 self=znippy-bz2 winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-bz2 | 744.39 | 11.62 | 1.38 | 80.60 |
| lbzip2 | **759.55** | 11.76 | **1.35** | 90.60 |
<!-- nornir:gen:end:benches -->

**zip**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-zip,unzip self=znippy-zip winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-zip | **2806** | 3.10 | **0.36** | 119.50 |
| unzip | 294.17 | 0.99 | 3.48 | 3.10 |
<!-- nornir:gen:end:benches -->

**jar**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-jar,jar self=znippy-jar winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-jar | **2814** | 3.19 | **0.36** | 119.20 |
| jar | 372.87 | 0.99 | 2.75 | 1.80 |
<!-- nornir:gen:end:benches -->

###### Single core variants — one core each

**gzip**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-gz_st,gzip,pigz self=znippy-gz_st winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-gz_st | 655.62 | 0.99 | 1.56 | 239.80 |
| gzip | **684.25** | 0.98 | **1.50** | 1.90 |
| pigz | 589.32 | 1.27 | 1.74 | 2.50 |
<!-- nornir:gen:end:benches -->

**bzip2**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-bz2_st,bzip2,lbzip2_st,pbzip2 self=znippy-bz2_st winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-bz2_st | **129.29** | 1.01 | **7.92** | 75.60 |
| bzip2 | 60.02 | 1 | 17.06 | 5.10 |
| lbzip2_st | 70.64 | 1 | 14.50 | 7.30 |
| pbzip2 | 60.29 | 1 | 16.98 | 41.80 |
<!-- nornir:gen:end:benches -->

**zip**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-zip_st,unzip self=znippy-zip_st winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-zip_st | **1354** | 0.98 | **0.76** | 12.10 |
| unzip | 294.17 | 0.99 | 3.48 | 3.10 |
<!-- nornir:gen:end:benches -->

**jar**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-jar_st,jar self=znippy-jar_st winner=row -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-jar_st | **1356** | 0.98 | **0.76** | 12.20 |
| jar | 372.87 | 0.99 | 2.75 | 1.80 |
<!-- nornir:gen:end:benches -->

<sub>`cores_busy` = avg CPU cores busy during decode (12 = full saturation). `znippy-*` run all
cores; `_st` rows are pinned to one worker. 🏆 = beat a long-reigning record ([`records.toml`](records.toml)).</sub>

##### Latest run — 32 cores, 2026-07-23: the harness refused to publish it

<!-- nornir:gen:start:bench_status -->
**Not benched.** This repo contributed no numbers to the latest run.

| | |
|---|---|
| `nornir-bench` entrypoint | `examples/nornir-bench.rs`, `osm-katana/examples/nornir-bench.rs` |
| criterion `[[bench]]` targets | 15 (`Cargo.toml` ×5, `lgz/Cargo.toml` ×1, `linflate/Cargo.toml` ×2, `ljar/Cargo.toml` ×2, `lzip-parallel/Cargo.toml` ×1, `osm-katana/Cargo.toml` ×4) |
| host-stamped result | **none** |

An entrypoint exists but produced no run — it was not part of the latest pass, or it exited without emitting a `BenchRun`.
<!-- nornir:gen:end:bench_status -->

The published tables above are **untouched** by this run, and that is deliberate: a
preview may never overwrite a measured, published table. `nornir docs render` hides a
preview run from every marker that has not opted in with `preview=yes`, so the blocks
below are the only ones it can reach.

Nothing here failed. The decoders did exactly what they were asked and the roundtrip
tests passed — but the OSM-XML input was **24 MB against the 1024 MB heavy floor**, so
the harness declined to emit a publishable `BenchRun` for the whole run rather than let
an under-floor pass become a number somebody quotes. Read every row below as a
**preview**: honest measurements of a workload too small to publish.

**gzip — preview**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-gz,pigz,gzip preview=yes self=znippy-gz winner=row -->
**v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-gz | **12800** | 13.87 | **0.08** | 733.50 |
| pigz | 459.19 | 1.23 | 2.23 | 2.10 |
| gzip | 538.95 | 1 | 1.90 | 1.70 |
<!-- nornir:gen:end:benches -->

**bzip2 — preview**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-bz2,lbzip2,bzip2,pbzip2 preview=yes self=znippy-bz2 winner=row -->
**v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-bz2 | 1164 | 26.60 | 0.88 | 188.90 |
| lbzip2 | **1296** | 29.37 | **0.79** | 259.90 |
| bzip2 | 46.11 | 1 | 22.21 | 4.80 |
| pbzip2 | 46.21 | 1 | 22.16 | 85.60 |
<!-- nornir:gen:end:benches -->

**one core each — preview**
<!-- nornir:gen:start:benches cols=decompress_mbs,cores_busy,decompress_s,peak_mem_mb compare=znippy-gz_st,gzip,znippy-bz2_st,bzip2,lbzip2_st,pbzip2 preview=yes self=znippy-gz_st,znippy-bz2_st winner=row -->
**v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23**

| workload | decompress_mbs | cores_busy | decompress_s | peak_mem_mb |
|---|---|---|---|---|
| znippy-gz_st | **1313** | 1 | **0.78** | 239.30 |
| gzip | 538.95 | 1 | 1.90 | 1.70 |
| znippy-bz2_st | 56.33 | 1 | 18.18 | 74.40 |
| bzip2 | 46.11 | 1 | 22.21 | 4.80 |
| lbzip2_st | 54.91 | 1 | 18.65 | 6.90 |
| pbzip2 | 46.21 | 1 | 22.16 | 85.60 |
<!-- nornir:gen:end:benches -->

**OSM convert — preview, and the arm that put the whole run under the floor**
<!-- nornir:gen:start:benches cols=convert_mbs,cores_busy,convert_s,peak_mem_mb compare=osm-katana,osm-katana_st,osmium preview=yes self=osm-katana winner=row -->
**v0.1.20 · PREVIEW · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · corpus 1024 MB · osm-katana input 24 MB · min-of-3 · 32 cores · 2026-07-23**

| workload | convert_mbs | cores_busy | convert_s | peak_mem_mb |
|---|---|---|---|---|
| osm-katana | **200** | 3.17 | **0.12** | 301.70 |
| osm-katana_st | 72.73 | 0.97 | 0.33 | 262.90 |
| osmium | 68.57 | 1.31 | 0.35 | 35.40 |
<!-- nornir:gen:end:benches -->

<sub>No `zip` / `jar` rows: those arms did not run in this pass. The host string above is
the harness's own — it composes `machine` from the CPU/RAM/corpus it measured and ignores
`NORNIR_MACHINE`, so this run reads as its Threadripper rather than as `oden`. To promote
any of this to a published table: `NORNIR_ZOOMIES_CORPUS_MB=1024` **and** an osm-katana
input ≥ 1024 MB (or `NORNIR_OSM_KATANA_CLIP=<region>` against a planet file).</sub>

##### Compressor benchmarks

Everything above is decode. [`ldeflate`](https://crates.io/crates/ldeflate) (published
2026-08-03) is the workspace's one **write**-side crate — the parallel DEFLATE/zlib
engine plus the gzip, ZIP and JAR deflators built on it. It gets its own section rather
than a column above, because a compressor cannot be raced the way a decoder can: the
bake-off reconstructs *one fixed archive*, while `gzip -6` and this crate emit different
bytes, so MB/s alone ranks nothing. The contested number is what the **schedule** costs
against the *same crate's* serial path over the same objects at the same level — so the
baseline row is `compress_objects_serial`, not `gzip`.

`plan::compress_objects` is the only scheduler in the crate. gzip, ZIP and JAR route
through it and differ **only in framing** — `Container::wrap` puts a header and trailer
around a body the planner already produced, and the three containers added no scheduling
code at all. The planner picks a unit kind per object by size: `Unit::Batch` below
`coalesce_below = 64 KiB` (one task drains many, reusing one encoder), `Unit::Whole`, or
`Unit::Segment` above 4 MiB (cut at `Z_FULL_FLUSH`, so one big blob cannot serialise a
worker). **`Unit::Batch` covers 93.8% of real git objects** — measured over 48 917 of
them — which is why one `Config` serves both git and LFS. `limits.rs` refuses an
amplifying request past `MAX_PIECES = 262 144` rather than attempting it.

###### Scaling — the same crate against its own serial path

<!-- nornir:gen:start:benches best=mb_per_sec:high cols=mb_per_sec,cores_busy,speedup_vs_serial,efficiency_pct compare=deflate.repack_git_objects_st,deflate.repack_git_objects_mt1,deflate.repack_git_objects_mt2,deflate.repack_git_objects_mt4,deflate.repack_git_objects_mt8,deflate.repack_git_objects_mt16,deflate.repack_git_objects_mt self=deflate.repack_git_objects_mt -->
**oden · AMD Ryzen Threadripper PRO 3975WX 32-Cores · 499 GiB · 30 770 real git objects,
715.4 MB, median 986 B · level 6 · min-of-3 · load 40–47 · 2026-08-04**

| workload | mb_per_sec | cores_busy | speedup_vs_serial | speedup ÷ cores held |
|---|---|---|---|---|
| deflate.repack_git_objects_st | 72.5 | — | 1.00 | — |
| deflate.repack_git_objects_mt1 | 69.2 | 0.93 | 0.95 | 1.02 |
| deflate.repack_git_objects_mt2 | 137.1 | 1.84 | 1.89 | 1.03 |
| deflate.repack_git_objects_mt4 | 258.5 | 3.55 | 3.57 | 1.01 |
| deflate.repack_git_objects_mt8 | 424.4 | 5.86 | 5.86 | 1.00 |
| deflate.repack_git_objects_mt16 | 621.1 | 8.48 | 8.57 | 1.01 |
| **deflate.repack_git_objects_mt** | **842.7** | **12.15** | **11.63** | **0.96** |
<!-- nornir:gen:end:benches -->

<sub>**Read the last column.** Above 8 threads the `speedup_vs_serial` column measures
**the box, not the code** — oden is shared and this crate takes every core it can get, so
a run that asks for 32 threads and is given 12.15 cores returns 11.63×. Across a 7-run
batch on 2026-08-03 the 32-thread speedup spanned **10.38×–23.20× (123.5% spread)** while
the **CPU cost of the schedule held to 1.025×–1.054× of serial**, and efficiency against
cores *actually held* stayed in a 94.9–97.6% band at every occupancy. An earlier headline
of **28.30× / 88% / 2266 MB/s** is not refuted — carried forward it predicts 29.2–31.2× at
that occupancy — but it was a statement about machine occupancy dressed as one about the
crate, it cannot be reproduced on a shared box, and it is retired as a crate claim.
Full tables, the negative controls and both falsified hypotheses: [`benchmarks.md`](benchmarks.md).</sub>

###### Never compress what is already compressed

`precompressed.rs` gates the entropy coder on **hint → extension → magic-byte probe** — no
entropy estimate, no tunable threshold, at most 16 byte compares. Acting on a `store`
verdict is free because `stored_deflate` emits RFC 1951 §3.2.4 stored blocks: a
length-prefixed `memcpy`, *not* `flate2` at level 0, which would still build, reset and
walk the encoder.

**oden · 24 real git packfiles + pack indexes · 113.8 MiB · level 6 · min-of-3 · decided on
CPU seconds · load 66 · 2026-08-04**

| probe | cpu-s per GiB | wall MB/s | ratio |
|---|---|---|---|
| off — deflate everything | 22.85 | 184.8 | 1.0041 |
| **on — store the already-compressed** | **0.99** | **724.6** | 0.9999 |

**→ 23.1× less CPU, 95.7% saved, output +0.41% larger.**

<sub>Decided on **CPU seconds**, not wall time: at load 66 the wall column is noise (the
same arm read 267.5 / 2075.5 MB/s on a quieter box) while `cpu-s per GiB` moved under 2%.
**Quote the saving, not the multiplier** — across four runs on 2026-08-03/04 the multiplier
wandered 19.4×–23.1× because its denominator is ~1 cpu-s/GiB, while the saving held at
94.8–95.7% and the size penalty came out **byte-identical (+0.41%) every time**. It
independently reproduces znippy's own 92–95% figure. Negative controls, same harness:
with the probe given nothing to find (git loose objects) it collapses to **1.03×**, and
with the probe vetoed by `ContentHint::Compressible` over the *same* packfiles to
**1.04×** at byte-identical output — so the 23× is a property of the probe.</sub>

##### OSM converter

`osm-katana` (in-repo sub-crate) converts OSM-XML/PBF → GeoParquet all-core on a no-barrier
gatling pipeline, head-to-head with the legacy reference tools `osmium` / `osmconvert` for the
same input. One convert pass, min-of-3. Columns: MB/s, cores busy, wall seconds, peak RSS (MB).

<!-- nornir:gen:start:benches cols=convert_mbs,cores_busy,convert_s,peak_mem_mb compare=osm-katana,osmium,osmconvert self=osm-katana -->
**v0.1.19 · AMD Ryzen 9 7900 12-Core Processor · 61 GiB · corpus 1024 MB · min-of-3 · 12 cores · 2026-07-13**

| workload | convert_mbs | cores_busy | convert_s | peak_mem_mb |
|---|---|---|---|---|
| osm-katana | 115.40 | 1.35 | 0.21 | 307.70 |
| osmium | 126.20 | 1.31 | 0.19 | 36.60 |
| osmconvert | 136.70 | 0.91 | 0.18 | 267.20 |
<!-- nornir:gen:end:benches -->

<sub>`convert_mbs` = input MB / wall; `cores_busy` = average CPU cores busy (12 = full saturation
on this box). `osm-katana` runs all cores; `osmium` / `osmconvert` are the single-stream C++
references, benched only when installed. By default a small synthetic OSM-XML corpus exercises the
emit path on any box — the HEADLINE number comes from the off-box planet run
(`NORNIR_OSM_KATANA_INPUT=<planet.osm> nornir bench run znippy-zoomies`). Region **clip** headline
(cut Sweden out of the planet with our tool): add `NORNIR_OSM_KATANA_CLIP=10.9,55.3,24.2,69.1`.</sub>

###### Staged pipeline

The classic planet chain — each stage eats the **previous stage's output file**:
`xml2-pbf` (OSM XML/bz2 → PBF) → `pbf2-geo` (PBF → GeoParquet) → `geo2-arrow` (GeoParquet → Arrow
IPC). `NORNIR_OSM_KATANA_CHAIN=1 nornir bench run znippy-zoomies` warehouses one row per stage
(`stage_mbs` = that stage's input MB / wall); point `NORNIR_OSM_KATANA_INPUT` at a planet for the
heavy chain. Off by default — it adds a full three-stage pass — so the table lands after that run.

##### Docs

- **[Manual](docs/manual.pdf)** · **[Idiot's guide](docs/idiot_guide.pdf)**

##### License

MIT. The `stree` module retains Ragnar Groot Koerkamp's copyright notice (see `LICENSE`).


