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

mikronova/modgunn

.nornir/reasoning.md

Móðguðr — reasoning (f0, the whole)

Class: reasoning. The why, the trade-offs, and every fork not taken. The descriptive shape we run is in design.md, which by rule mentions none of this. If you want "why two engines?", "why a leaf?", "why not depend on nornir?" — it is here. The deep analysis catalogs and the ingest roadmap live in modgunn-master-plan.md, engine-b-analyses.md and cve-warehouse-ingest.md (also reasoning-class).

Why this overall shape

The goal was to give holger a lean promotion gate and nornir a rich security plane from one body of security logic, without the two coupling to each other and without a dependency cycle. Every structural choice below serves that goal: one crate, one verdict type, two feature-gated engines, one leaf edge to skade.

Why a leaf crate (and why modgunn → nornir is forbidden)

nornir owns the viz + orchestration; holger owns promotion. Both need verdicts. If the security domain lived in nornir, holger would depend on nornir (heavy) to get a gate; if it lived in holger, nornir would depend on holger. Extracting it into a leaf — depended on by both, depending on neither — is the only shape with no cycle. modgunn → skade is allowed because skade is itself a leaf (embedded Iceberg); modgunn → nornir is the exact edge that would recreate the cycle, so it is banned. modgunn reaches the shared warehouse through skade's tables, opening the same physical catalog.redb in the nornir namespace rather than calling nornir's API.

Why two engines split by a feature (not one, not two crates)

Why store advisory ranges and match at scan time (not expand at import)

Early cve_advisories stored affected_versions as exact versions and matched by equality. Importing a range-only OSV/NVD entry then required expanding the range against the full published version universe per package — which explodes row counts, is lossy (a version published after import silently reads "not affected"), and couples the advisory feed to a registry-version catalog you do not have offline. So we evolved to range-native matching: persist the OSV range events (introduced/fixed/last_affected) and evaluate versions::affected_by(eco, ranges, version) against the artifact's actual version at scan time. Expansion became lazy or unnecessary. Full rationale + the schema-v2 migration: cve-warehouse-ingest.md §0.

Why offline / airgap by construction

The government posture ("know exactly what happens to the smallest bit") rules out live feed access on the secure side. OSV-Scanner and friends need the network. modgunn's advisory DB is warehouse-resident: the internet side fetches + seals a .znippy, it is carried across the gap, and the secure side runs modgunn warehouse import — db_rev is the import generation and a component of the verdict cache key, so a scan is reproducible against a pinned corpus.

Why the split of domain vs build-orchestration (the nornir cutover)

Only the security domain moved into modgunn (scan, advisory feeds, to_cyclonedx, verdict). Build-orchestration stayed in nornir — resolving a repo's dep closure (cargo metadata, sibling-repo cloning, sbom_components persistence) is nornir's build concern, not a security primitive. nornir computes Components and hands them to modgunn; modgunn returns verdicts. Extracting the verdict also deleted nornir's old all-High severity mapper: real severity/EPSS/KEV now drives the decision, and matching happens once, in modgunn. The extraction plan and consumer touch-points: modgunn-master-plan.md §6.

Where the deep "why" lives