<!-- nornir:class: design -->
# Runtime substrate trait — the interface (design)
`korp_collectors::runtime` (feature `daemon`) is the **object-safe interface** that decides
*WHERE* a collector daemon (or, later, a knut pipeline) runs, decoupled from its *logic*. A
collector's behaviour is substrate-agnostic (`Collector` against `CollectorCtx`); this
trait is the seam that spawns it. See the workspace design
`runtime-aortas-design-2026-07-29.md`.
## Leaf discipline
This crate depends on **neither korp nor jera nor kube**, so the trait pulls **no new
deps** — pure `serde` + `anyhow` (both already under `daemon`). The concrete
implementations (`JeraRuntime`, `KubeRuntime`) live in **korp**, which owns jera / aegir /
skidbladnir. Keeping the interface here lets both korp and facett-demo consume it without
the `facett ⇄ korp` cycle.
## The shapes
* **`RuntimeKind`** — `Jera` (metal in-process jera DAEMON job — default) · `JeraDistributed`
(DESIGN-ONLY, a stubbed `TODO`) · `Kube` (full Kubernetes via kube-rs SSA, zero-shell).
* **`UnitSpec`** — the backend-neutral description of a runnable: `name`, `UnitKind`
(`Daemon` → Deployment/StatefulSet · `Cron` → CronJob · `OneShot` → Job), `Entry`
(`InProcess{collector}` = the Jera-metal path · `Image{image,argv}` = the Kube/distributed
path, `korp collector run <name>`), `config` (a ConfigMap on Kube / the `CollectorCtx`
config on metal), `StorageSpec` (durable dir ⇒ PVC + StatefulSet; S3 bucket ⇒ the rustfs
Service), `Resources`, env / secret_env, image, namespace. Builder + serde round-trip.
* **`Handle`** — opaque `{runtime, name, id}` (the jera `job_id` / the k8s
`<ns>/<kind>/<name>`) for `stop`/`status`.
* **`RunStatus`** — `{phase, healthy, logs}` — the live phase label + a health flag.
## The trait
```rust
pub trait Runtime: Send + Sync {
fn kind(&self) -> RuntimeKind;
fn start(&self, spec: &UnitSpec) -> Result<Handle>; // idempotent where the substrate allows
fn stop(&self, h: &Handle) -> Result<()>; // idempotent
fn status(&self, h: &Handle) -> Result<RunStatus>;
fn reconcile(&self, desired: &[UnitSpec]) -> Result<()>; // the Settings toggle / matrix ▶Run route
}
```
Object-safe (`&self`, no generics), so a `Box<dyn Runtime>` selects the substrate at
runtime from the persisted per-collector choice. Proven by a mock in the crate tests
(`runtime_is_object_safe_via_a_mock`) plus key/serde round-trips.