<!-- nornir:class: reasoning -->
# MSI build → draupnir's ONE OCI engine (killing a podman `-v` twin)
**dup-audit P1.** Skidbladnir shelled `Command::new("podman") -v …` in two places; the
constellation law is ONE container engine (draupnir's `backend-oci`, pure-Rust bollard over the
podman/Docker REST API), zero shell. draupnir landed `run_to_completion_with_binds` (host `-v`
binds) for exactly this — so the *run-an-image* twin now routes through it.
## What changed
`src/msi.rs` `WixBuilder::build` — the WiX v4 / Wine MSI build is a **`podman run --rm -v
work:/work <image> wine wix.exe build`**, i.e. run-an-existing-image-to-completion with one bind.
That maps 1:1 onto draupnir:
```
WXS render + harvest (staged workdir) ── unchanged, always real
│
▼
#[cfg(feature = "msi-oci")] ── zero-shell path
BootSpec::container("skidbladnir-wix-msi", image)
.with_cmd([wine, wix.exe, build, /work/Product.wxs, -o, /work/out.msi])
ContainerBoot::run_to_completion_with_binds(spec, RunOptions::default(),
["<workdir>:/work"]) ← draupnir, ONE engine
└─ exit_code != Some(0) → honest bail (never a fake MSI)
│
#[cfg(not(feature = "msi-oci"))] ── lean fallback (kept, honest)
podman_available() ? Command::new("podman") run : bail("needs podman …")
│
▼
/work/out.msi ──(bind)──▶ work/out.msi ──▶ copy to `out`
```
- `msi-oci` is **default-off** (the lean core never pulls draupnir's bollard/tokio); the podman
subprocess remains only as the feature-off fallback, with an honest message pointing at
`--features msi-oci`.
- Absolute `/work/...` paths in `cmd` remove the need for podman's `-w`.
- Build green both ways; `build_pipeline_is_real_and_honest_without_podman` is now cfg-aware
(both paths error honestly + keep the "WXS rendered" stage real).
## Also routed now: `src/pack.rs` (`pack-oci`)
`PodmanBuild::build_into` does `podman **build**` (image build from a rendered Containerfile) +
`podman create` + `podman cp` (extract `/out`). draupnir since grew the missing surface —
`ContainerBoot::build_image` (tars the context → daemon `/build`) and `ContainerBoot::extract_path`
(the `create`+`cp` twin) — so the *second half* of this twin now routes too, behind the
**`pack-oci`** feature (default-off, same lean posture as `msi-oci`).
```
render Containerfile → ctx + tag ── unchanged, shared
│
▼
#[cfg(feature = "pack-oci")] ── zero-shell path
ContainerBoot::build_image(ctx, "Containerfile", tag) ← draupnir, ONE engine
ContainerBoot::extract_path(tag, "/out", tmp) ← create+cp twin
└─ extract roots the tar at the basename → tmp/out/…
└─ copy_tree_contents(tmp/out, into) ── flatten to the `cp {cid}:/out/.` layout
(both engines leave `into` BYTE-IDENTICAL → same `walk` manifest)
│
#[cfg(not(feature = "pack-oci"))] ── lean fallback (kept, honest)
runtime_available() ? podman build/create/cp : bail("needs podman … or --features pack-oci")
│
▼
walk(into) → sorted relative paths (engine-agnostic manifest)
```
- The one subtlety: draupnir's `extract_path("/out", …)` roots its tar at the path **basename**
(`out/…`), whereas `podman cp {cid}:/out/.` drops the *contents* of `/out` straight into `into`.
`copy_tree_contents` flattens `out/`'s contents into `into` so the two engines are byte-identical
— proven by `copy_tree_contents_flattens_extracted_out_dir_into_staging` (pure, no daemon).
- Convenience umbrella `oci = ["msi-oci", "pack-oci"]` routes **both** builds through the one engine.
## Reuse (Law #1)
Zero new container code here — the whole engine is draupnir's; this only swaps the call. Second
engine? none. Shell? none (under the feature).