Gunnar

mikronova/modgunn

.nornir/framework-guide.md

<!-- nornir:class: guide -->
# framework — guide (About · 🧰 Tools · lifecycle, from the CLI)

> **Class:** `guide` · **Function axis:** `framework`. What the surfaces do and how to drive
> them. Shape + rationale: [`framework-design.md`](framework-design.md).

modgunn shares the constellation-wide **common framework** (the same About + Tools + lifecycle
every suite app has). Because modgunn is a CLI, the surfaces are four verbs.

## About — who is this?

```console
$ modgunn about
Móðguðr — modgunn  v0.0.5
  the nordisk security + SCIP world-delta gatekeeper (Gjallarbrú)
  Guards the bridge: scrutinizes what passes (CVE / license / provenance verdict) …
  · Engine A — artifact security VERDICT (scan: multi-ecosystem CVE / license / provenance)
  · Engine B — SCIP whole-world dep-tree BEHAVIOUR-DELTA (scip: tripwires / graph-algs / anomaly)
  repository: git@codeberg.org:nordisk/modgunn.git

$ modgunn about --json   # the stable oracle a robot asserts
{ "name": "modgunn", "display_name": "Móðguðr", "version": "0.0.5", "subtitle": "…gatekeeper…", "engines": [ … ], "framework": [ … ] }
```

`about` needs **no** extra feature — it works in the lean `scan` build too.

## 🧰 Tools — one-click demo data through jera

```console
$ modgunn tools                     # list the menu (needs --features framework)
modgunn 🧰 Tools (jera-backed demo actions):
  🩺 Demo advisory feed — generate + scan  [starts: modgunn-demo-feed → view: `modgunn warehouse scan`]
      Start a pinned demo container (jera) that writes a demo OSV advisory …

$ modgunn tools --json              # the roster oracle

# run the demo: start the container (jera) → it writes a demo OSV doc into --out
$ modgunn demo advisory-feed --out ./demo-advisories
demo advisory-feed → jera ContainerSpec: image=docker.io/library/alpine:3.20.3 name=modgunn-demo-feed mount=./demo-advisories:/out
(plan only) rebuild with `--features framework-oci` to start the container live; then `modgunn warehouse import --osv ./demo-advisories`
```

Building with `--features framework-oci` makes `demo advisory-feed` actually start the container
(routed THROUGH draupnir's ONE OCI engine, as a tracked `jera` job) and write the demo OSV file.
Then fold + view it:

```console
$ modgunn warehouse import --warehouse /wh --rev demo --osv ./demo-advisories
$ modgunn warehouse scan   --warehouse /wh
```

**In Rust** (consuming the same seam):

```rust
let spec = modgunn::tools::demo_feed_spec("./demo-advisories"); // a jera ContainerSpec
# #[cfg(feature = "framework-oci")]
let outcome = modgunn::tools::run_demo_feed("./demo-advisories")?; // run THROUGH jera as a job
```

## Service lifecycle — through the ship (skidbladnir)

modgunn declares its world-delta service through the SHARED ship, not a hand-written unit:

```console
$ modgunn service --unit
[Unit]
Description=Móðguðr — the nordisk security + SCIP world-delta gatekeeper
…
[Service]
User=modgunn                                   # its OWN dedicated system user (L5)
Environment=MODGUNN_WORLDDELTA_ADDR=127.0.0.1:50071
ExecStart=/usr/local/bin/modgunn world-delta watch
Restart=on-failure
…

$ modgunn service --json                       # unit · user · port · fat/thin posture
$ modgunn service --unit --user-scope          # a `systemctl --user` dev unit (runs as YOU, no User=)
```

The dedicated `modgunn` user is provisioned at install time via
`skidbladnir::systemd::ensure_system_user("modgunn", …)` — the SAME code `nornir`→`nornir`,
`holger`→`holger` use. (The watcher daemon the unit runs is future work.)

## Feature matrix — what to compile in

| you want | build with |
|---|---|
| `about` only (lean) | default (`scan`) — nothing extra |
| `tools` / `demo` plan + `service --unit` | `--features framework` |
| the demo container to actually run | `--features framework-oci` |
| the binary (all verbs) | `--features "warehouse framework"` |

## Build atoms — kind × linkage (`cargo xtask build`)

The feature flags above are the raw cargo axis; the **named** builds modgunn ships are declared
once as data in [`build-variants.toml`](build-variants.toml) and resolved by the ONE build verb,
`cargo xtask build` (nornir-build-thing's `suite_cli`). **ONE atom**, and it is a row of the
constellation build matrix:

| suite | atom | role | linkage | bin |
|---|---|---|---|---|
| `ORIGINAL` | `modgunn` ⭐ | client | **fat** | `modgunn` |

The CLI: Engine A+B + the skade warehouse seam compiled IN (`warehouse = ["dep:skade", …]`), so it
works with no server running — which is what the matrix means by a *fat client*. A batch CLI that
does one job and exits.

```console
$ cargo xtask build --suite ORIGINAL                      # every cargo atom of the delivery
$ cargo xtask build --suite ORIGINAL --atom modgunn       # cargo build -p modgunn --bin modgunn --features scan,scip,warehouse,rustsec,provenance-verify --no-default-features --profile release
$ cargo xtask build --suite ORIGINAL --list               # resolve and print; build nothing
$ dwarves buildthing modgunn --suite ORIGINAL             # the same resolver, from outside the repo
```

**modgunn is NOT a server, has no wire and has no UI.** The matrix gives every member five shapes
(`<x>-server` · `<x>` · `<x>-cli` · `<x>-ui` · `<x>-solo`); modgunn fills exactly ONE, and the other
four are declared GAPS rather than phantom atoms — because carrying the `suites` tag is what claims
Build-Thing status, and an atom that claims it while building nothing is a promise the repo cannot
keep. The `service --unit` verb above renders a systemd unit; it does not start one, the module
behind it is not even compiled into the shipped `modgunn` atom, and the unit's `ExecStart` names
`modgunn world-delta watch`, a verb the CLI does not have. Full measurements:
[`suite-atoms-design.md`](suite-atoms-design.md).

**The asymmetry guard.** modgunn is the cycle-breaker holger + nornir both depend on, so they are
*upstream*: a fat variant may embed only **skade** (downstream). A variant that tried `embeds =
["nornir"]` is rejected by `BuildVariantSet::validate`.