Gunnar

mikronova/modgunn

.nornir/warehouse-guide.md

# warehouse — guide

> **Class:** `guide` · audience: someone opening the shared catalog to import advisories or
> scan a fleet. The *what* is in [`warehouse-design.md`](warehouse-design.md); the *why* in
> [`warehouse-reasoning.md`](warehouse-reasoning.md). Needs `--features warehouse`.

## f0 — open, import, scan (CLI)

```sh
# import an advisory generation (see feeds-guide.md for the feed flags)
modgunn warehouse import --warehouse <root> --rev 2026-07-04-a --osv advisories/osv/

# full fleet scan: every repo's sbom_components × the latest advisories
modgunn warehouse scan --warehouse <root>

# incremental CDC scan: only components added since a snapshot; prints next watermark
modgunn warehouse scan --warehouse <root> --since 4211

# what's imported
modgunn warehouse stats --warehouse <root> --ecosystem cargo

# persist an A23 package-behaviour analysis (verdict ledger), then read it back
modgunn behavior --path package.json --warehouse <root>          # append the vector + verdict
modgunn warehouse behavior-log --warehouse <root>                # latest per (ecosystem, subject)
modgunn warehouse behavior-log --warehouse <root> --ecosystem npm --subject left-pad-helper
```

`scan` prints one line per vulnerable dependency (`repo name version advisory_id severity`)
then a summary with the high/critical count and, for `--since`, the `next --since <watermark>`
to poll from. Add `--vex <dir>` to drop consumer-suppressed advisories, `--sql` to use the
DataFusion pushdown (needs `warehouse-sql`; degrades to in-memory otherwise).

## f0 — in code

```rust
use modgunn::warehouse::Warehouse;
use std::path::Path;

let wh = Warehouse::open(Path::new(root))?;   // or open_read_only when a server holds the lock
wh.ensure_modgunn_tables()?;

wh.append_advisories("2026-07-04-a", &advisories)?;      // a new generation
let db = wh.load_advisory_db(Some("cargo"))?;            // latest db_rev, one ecosystem
let findings = wh.scan_fleet()?;                         // Vec<FleetFinding>

// A23 behaviour verdict ledger — persist a parsed PackageBehavior, read it back.
let behavior = modgunn::behavior::parse_npm_package(package_json)?;
wh.append_package_behavior("npm", &behavior)?;           // vector + derived verdict, append-only
let records = wh.load_package_behaviors()?;              // Vec<BehaviorRecord>, latest per (eco, subject)
assert_eq!(records[0].behavior, behavior);              // round-trips field-identical
```

## f2 — incremental refresh loop

```rust
// remember the last watermark; poll cheaply, only scan the delta
let (findings, next) = wh.scan_sbom_delta(Some(last_watermark))?;
// … act on findings …
last_watermark = next.unwrap_or(last_watermark);
```

Use `wh.commit_seq()?` as an even cheaper "did anything change at all?" poll before calling
`scan_sbom_delta`. `wh.table_delta("cve_advisories", from)?` gives the raw changed rows for any
owned table.

## f3 — capturing an Engine B snapshot (feature `scip` + `warehouse`)

```rust
wh.capture_world_snapshot(&snapshot)?;                       // world_snapshots + dep_edges + scip_symbols
wh.record_behavior_delta_findings(&delta)?;                 // behavior_deltas rows
```

## Gotchas

- If a running `nornir-server` holds the redb write lock, use `Warehouse::open_read_only` — it
  copies the catalog aside and reads a consistent snapshot without contending.
- `scan_fleet` assumes `cargo` for SBOM rows until nornir adds an ecosystem column
  (`DEFAULT_ECOSYSTEM`); cross-ecosystem fleets will mis-match until then.
- A missing `sbom_components` table is not an error — it just means "no watermark" / an empty
  fleet, so a fresh warehouse scans cleanly.
- Imports and a live server writer are single-writer coordinated — schedule imports when the
  server is down, or import through the read-only-safe path.