Gunnar

nordisk/Skidbladnir

.nornir/orchestrate-guide.md

# orchestrate — guide

> **Class:** `guide` · audience: a techie bringing up a lake of products. The *what* is in
> [`orchestrate-design.md`](orchestrate-design.md).

## Compute the bring-up order

```rust
use skidbladnir::{orchestrate, product::ComponentSpec};

let products: Vec<_> = [ComponentSpec::nornir_server(), ComponentSpec::holger()]
    .iter().map(|c| c.as_product()).collect();

let waves = orchestrate::start_waves(&products)?;   // Vec<Vec<String>>
// e.g. [["nornir-server"], ["holger"]] — holger's `after nornir-server` puts it in a later wave
```

Or straight from a whole-lake spec:

```rust
use skidbladnir::{config::LakeSpec, pki::PkiSource, product::ComponentSpec};

let lake = LakeSpec {
    version: "20260704".into(),
    pki: PkiSource::SelfGenerated,
    products: vec![ComponentSpec::nornir_server(), ComponentSpec::holger()],
};
let waves = lake.start_waves()?;   // lib.rs:137
```

## Start each wave (concurrency within a wave)

```rust
for wave in waves {
    std::thread::scope(|s| {
        for product in &wave {
            s.spawn(move || { /* install::apply / start this product */ });
        }
    });
}
```

Products in the same wave have no ordering constraint between them, so start them concurrently;
wave `i+1` only begins once wave `i` is up.

## Tear down in reverse

```rust
let order = orchestrate::teardown_order(&products)?;   // dependents before their dependencies
```

## Emit telemetry to the warehouse

```rust
use skidbladnir::events::{JsonlSink, EventEmitter, op, phase, status};

let sink = JsonlSink::under_warehouse(&warehouse_root)?;   // <root>/airgap_events.jsonl
let em = EventEmitter::new("run-2026-07-04", &sink);
em.emit("holger", op::INSTALL, phase::START, status::RUNNING, "unpacking", &["nornir-server".into()]);
// … later …
em.emit("holger", op::START, phase::END, status::OK, "healthy", &[]);
```

Read them back (sorted by `seq`) with `skidbladnir::events::read_jsonl(&path)?`.

## Gotchas

- A **dependency cycle** is a hard error naming the stuck products — fix the `after` edges.
- Deps that name something outside your product set (`network.target`) are ignored, not errors —
  they are init-system concerns, not products.
- Emission is **best-effort**: a sink failure logs to stderr and never aborts the bring-up. The
  events are observability, not a gate.
- The sink writes under the **warehouse root**, never `/tmp` — pass a real warehouse path.