Gunnar

mikronova/draupnir

.nornir/oci-bind-mounts.md

<!-- nornir:class: design -->
# OCI bind mounts — the mount-aware container path (kills the `podman -v` twin)

> **Why.** Skidbladnir's `msi.rs` (WiX v4 under Wine) and `pack.rs` shell out to
> `Command::new("podman") … -v host:ctr …` — a zero-shell-charter violation and a
> second container engine (dup-audit P1). They need **bind mounts** to pass the WXS
> in and the `.msi` out. draupnir owns the ONE OCI engine, but its create path had
> no bind support. This adds it **additively**, so those twins can route through
> `ContainerBoot::run_to_completion_with_binds` instead of a podman subprocess.

## The seam (all additive — `BootSpec` untouched)

The shared `BootSpec` has no `Default`, so a new field would break every struct
literal across nornir/korp/dwarves. Instead binds ride the **create path** as an
explicit `&[String]` (`host:container[:opts]`, `-v` semantics), threaded through
new `*_with_binds` twins; the bind-free entries **delegate** to them, so every
existing caller is byte-for-byte unchanged.

| bind-free (unchanged) | mount-aware (new) | note |
|---|---|---|
| `Engine::create_body` | `Engine::create_body_with_binds` | pure body builder; empty binds ⇒ identical body (`HostConfig.binds` stays `None`) |
| `Engine::create_and_start` | `Engine::create_and_start_with_binds` | create+start with `HostConfig.binds` |
| `Boot::boot` | `ContainerBoot::boot_with_binds` | mount-aware boot |
| `ContainerBoot::run_to_completion` | `ContainerBoot::run_to_completion_with_binds` | the seam Skidbladnir consumes |

The run-to-completion **loop** (drain→poll→final-drain→stop) is factored into one
`drive_to_completion(backend, &machine, opts)` that both paths share — single-source,
no duplicated driver. `binds` empty ⇒ every path equals its bind-free original.

```
 Skidbladnir msi.rs / pack.rs                 draupnir (ONE OCI engine, bollard)
 ┌───────────────────────────┐                ┌──────────────────────────────────┐
 │ run_to_completion_with_binds(spec, opts,   │  boot_with_binds → create_and_start│
 │   ["/in:/work/in:ro",       │ ───────────▶ │   _with_binds → create_body_with_  │
 │    "/out:/work/out"])        │              │   binds → HostConfig.binds = -v    │
 └───────────────────────────┘                │  drive_to_completion (shared loop) │
   (replaces Command::new("podman") -v …)      └──────────────────────────────────┘
```

## Status
- draupnir: **landed** — builds default (no bollard) + `--features backend-oci`; test
  `create_body_with_binds_attaches_host_mounts_and_stays_parity_when_empty` green.
- Skidbladnir consumption (route `msi.rs`/`pack.rs` off `podman` onto
  `run_to_completion_with_binds`) = the **next task** (this unblocks it).