# archive — guide
> **Class:** `guide` · audience: a techie packing or reading bundles. The *what* is in
> [`archive-design.md`](archive-design.md); the *why* in [`archive-reasoning.md`](archive-reasoning.md).
## Pick an engine
```rust
use skidbladnir::archive::{Archive, TarZstd};
let eng = TarZstd::new(); // always available, always offline
```
For the REAL znippy-core engine (seal/read/verify/unpack via `../znippy`, so the bundle is the one
canonical `.znippy` — `znippy` CLI / DuckDB readable), build with `--features znippy` and swap the
constructor — the trait seam means nothing else changes:
```rust
# #[cfg(feature = "znippy")]
let eng = skidbladnir::archive::ZnippyArchive::new();
```
## Pack a staging tree
```rust
// staging_root holds your files under their relative paths (see pack-guide.md)
let manifest = eng.pack(&staging_root, "20260704", &bundle_path)?; // Archive::pack
assert!(manifest.verify_root()); // self-check
// a sidecar <bundle_path>.manifest.json is written beside the bundle
```
`manifest.entries` is one `EntryDigest { path, sha256, len }` per file; `manifest.root_sha256` is
the hash over all of them. This manifest is what the far side verifies.
## List and read without extracting everything
```rust
for path in eng.list(&bundle_path)? { println!("{path}"); }
let bytes = eng.read_entry(&bundle_path, "bin/app")?; // znippy: get_file random-access; tar: temp-extract
```
## Verify a received bundle
```rust
let manifest = skidbladnir::archive::load_manifest(&bundle_path)?;
let bad = eng.verify_entries(&bundle_path, &manifest)?; // Vec<String> of bad paths
assert!(bad.is_empty(), "tampered or incomplete: {bad:?}");
```
Prefer the `fetch` wrapper for the full receipt gate (manifest root + optional out-of-band SHA +
per-entry) — see [`move-guide.md`](move-guide.md).
## Ship only what changed (znippy delta)
With `--features znippy`, pack a patch against the previous manifest and apply it on the far side:
```rust
# #[cfg(feature = "znippy")] {
let patch = eng.delta(&old_manifest, &new_staging, "20260705")?; // ships only changed blobs
println!("shipped {} bytes, reused {} entries", patch.shipped_bytes(), patch.reused());
let new_manifest = eng.apply(&old_bundle, &patch, &new_bundle)?; // re-seals a real znippy bundle; same content root as a fresh pack
# }
```
On the default `TarZstd` engine `delta`/`apply` bail with a clear "use the znippy feature" message
(`archive.rs:143`) — they are not silently faked.
## Gotchas
- The manifest is a **sidecar** file (`<bundle>.manifest.json`), not inside the archive. Ship it
alongside the bundle, or regenerate it with `pack`.
- `TarZstd::new()` maps level 0 to zstd level 3; pass a level explicitly for a different ratio.
- znippy `read_entry` (`get_file`) bounds blob length against file size before allocating — a
corrupt/hostile index cannot OOM you — and blake3-verifies every chunk. Extraction rejects
`..`/absolute paths (zip-slip), and a tampered byte fails verification loudly.