Gunnar

nordisk/knut

.nornir/consolidation-seam-eval.md

<!-- nornir:class: design -->
# knut consolidation seam evaluation (jera / skidbladnir / draupnir / facett panels)

Seam audit against the constellation standard
(`workspace_nordisk/.nornir/consolidation-panels-maps-design.md`): *no app
re-implements service/systemd, job execution, provisioning, pane hosting, or map
drawing — there is ONE crate for each.* This is the read-only evaluation for
**knut**; each item states the gap and either the additive fix or the honest
reason it is deferred (a working, often feature-gated, hot path — forcing it now
would regress a green surface or pull an unrelated dep tree into knut).

## 1. jobs / provisioning (`jera` → `draupnir`)

### 1.1 `knut-pipelines` `server` feature = a bespoke `bollard` container manager — DEFER
`crates/knut-pipelines/src/server.rs` (`SparkServer`, feature `server`) drives the
Spark-Connect + SDP container up/down through **`bollard` 0.20** against podman's
REST API directly (`Cargo.toml:118`). This is the exact concern the consolidation
chain owns: **app → `jera` (job) → `draupnir` (OCI backend)**. It is effectively a
fourth independent bollard engine in the constellation (jera's `BollardEngine`,
draupnir's `backend-oci`, `nornir/src/viz/test_boot.rs`, and this).

**Target (when unblocked):** `SparkServer` submits a container job to
`jera::jobs`, and jera provisions via `draupnir::container` (the
`DraupnirContainerBootRunner` + `run_to_completion` seam that landed 2026-07-12).
knut would then drop its `bollard` dep entirely.

**Why deferred, not forced now:**
- The jera container consolidation is itself **still gated** — per
  `bencher_agents_problems.md` (2026-07-12 draupnir/jera responses) jera KEPT its
  `BollardEngine` and has NOT flipped its default off bollard; the repoint of
  `jera run_container` onto `draupnir::run_to_completion` is open jera/nornir work.
  Repointing knut onto jera before jera itself is consolidated would chase a
  moving seam.
- `server` is a **working, feature-gated** path (OFF by default — the default
  build never pulls bollard). It brings up a real Spark JVM and waits on a ready
  line; a rewrite is a genuine provisioning change that must be bench/-integration
  verified against a live podman, which the Loki bencher does not have. This is
  the same class as korp's deferred daemon adoption and draupnir's own deferred
  jera repoint.
- Adopting jera pulls jera + draupnir (+ their trees) into knut-pipelines for a
  feature that is a thin container up/down — the dependency cost wants the shared
  seam to settle first.

**Filed for:** the jera/draupnir owners to finish the bollard consolidation; then
a follow-up knut pass repoints `SparkServer` and removes `bollard`.

### 1.2 UI stream/repaint threads — jera N/A (not tracked jobs)
`knut-pipelines/src/gui.rs:677,740` and `knut-popviz/src/main.rs:93` use
`std::thread::spawn`, and `web.rs:189` / `knut-bifrost/src/flight.rs:336` use
`tokio::spawn`. These are **event-driven UI repaint pumps** and a Flight server
handler task — in-process, untracked, no durable ledger semantics. Same finding
as ordning's seam eval: `jera::jobs` routing is **not applicable** to pure
in-proc UI/stream threads. No change.

## 2. service / systemd (`skidbladnir`)

`knut-pipelines/src/server.rs:325 start_podman_socket_unit` auto-starts the
rootless `podman.socket` **systemd user unit over D-Bus** (zbus `StartUnit`), and
`bin/knut-spark-server.rs` is an up/stop/status/logs lifecycle CLI. This is
**external-unit lifecycle management** (bring up podman's socket so the container
API is reachable), not "run knut itself as a managed systemd service", which is
what `skidbladnir::service`/`Lifecycle` standardises. The overlap is marginal:
knut is not daemonising itself. **No forced change**; if knut ever ships its own
long-running service (e.g. a hosted pipeline runner), it should declare a
`skidbladnir::ServiceSpec` then — noted for that future.

## 3. facett panels — knut is ALREADY on the component-crate model (positive example)

Unlike korp/holger (welded egui, §1.2 of the constellation doc), knut already
**consumes shared facett component crates**, so there is no duplication to remove:
- `knut-popviz` (feature `gui`) renders through **`facett-pop`** (`Cargo.toml:58`)
  — the progress-bar/pop visual components, cut over from a local copy.
- `knut-pipelines` (feature `facett-adapter`) maps its Spark/SDP `ViewerState`
  onto **`facett-pipeline`**'s engine-agnostic `PipelineModel` via the
  `PipelineAdapter` trait (`Cargo.toml:90`), the facett types never leaking into
  knut's public API.

**Remaining (plan-only — facett is off-limits to this repo):** there is no
`facett-knut*` panel + `<guest>-embed` adapter (constellation §2) that would let
*another* app HOST knut's pipeline/pop viewer as a `Panel` tab (the reusable
`fn panel(endpoint) -> Box<dyn Panel>` shape). knut consumes facett panes; it does
not yet EXPORT one for embedding. Creating that adapter is facett-side work
(new `facett-knut*` component crate + a `knut-embed` adapter linking a thin gRPC
client) and is explicitly **out of scope for the knut repo** — captured here so
the facett/panel owner can pick it up under the extraction order
(dwarves → holger → korp → nornir → …knut).

## 4. map trait (`facett-map`) — N/A
knut has no map/geo surface. The selectable `MapLayer` trait (constellation §3)
does not apply. No action.

## Summary

| seam | knut status | action |
|---|---|---|
| jera jobs (container) | bespoke `bollard` in `server` feature | DEFER — gated on jera's own bollard consolidation; working feature-gated path |
| jera jobs (UI threads) | in-proc repaint/stream pumps | N/A (not tracked jobs) |
| skidbladnir service | external podman.socket unit mgmt only | no forced change; adopt if knut ever self-daemonises |
| facett panels (consume) | already uses facett-pop + facett-pipeline | none — no duplication (positive example) |
| facett panels (export) | no `facett-knut*` embed adapter | PLAN-only — facett-side, out of knut scope |
| facett-map | no geo surface | N/A |