Gunnar

nordisk/edda

.nornir/README.md

# 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` |
| **guest-host** | the reusable mechanics of hosting an untrusted **wasm32 guest** — fuel + wall-clock + memory/table ceilings that refuse **by name** (a configured `0` is a misconfiguration, never "unlimited"), a detached-CMS signature gate over the module's blake3 digest (fail-closed without the verifier), a census by digest, and the `alloc`/`result_len`/`plugin_call` calling convention. No product vocabulary: holger's plugin host and monetize's product host put their own ABI on top. `wasmtime` and `verify-modules` are features |
| **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 OFFLINE against a cached/pre-provisioned JWKS (airgap), or an opaque token ONLINE via `/userinfo`; 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-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 -->
<!-- nornir:gen:end:benches -->

## Tests

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

## Dependency graph

<!-- nornir:gen:start:depgraph -->
<!-- nornir:gen:end:depgraph -->

## License

MIT OR Apache-2.0.