use anyhow::Context;
use varve_core::Store;
struct Row {
name: String,
version: Option<String>,
kind: String,
known_kind: bool,
platform: String,
digest: String,
dispatch: &'static str,
present: bool,
layer: String,
realm: String,
}
const DISPATCHED: &str = "dispatched";
const HELD: &str = "held";
const UNKNOWN: &str = "unknown";
pub fn run(store: &Store, layer: Option<&str>, json: bool) -> anyhow::Result<()> {
let target = crate::export_target(store, layer)?;
let layers = crate::composition_for_export(&target)?;
let host = varve_core::host_platform();
let mut rows = Vec::new();
for l in &layers {
let payload = std::fs::read(l.entry.root.join("layer.json")).with_context(|| {
format!(
"cannot read the signed manifest of layer {} — the store entry is incomplete",
l.entry.layer
)
})?;
let manifest = varve_core::LayerManifest::parse(&payload)?;
for e in &manifest.entries {
let parsed = e.kind();
let kind = match &parsed {
Ok(k) => k.as_str().to_string(),
Err(varve_core::UnknownKind(raw)) => raw.clone(),
};
if parsed == Ok(varve_core::PayloadKind::Layer) {
continue;
}
let name = e
.annotations
.get("eu.pulseengine.tool")
.cloned()
.unwrap_or_else(|| "(unnamed entry)".to_string());
rows.push(Row {
version: e.annotations.get("eu.pulseengine.tool.version").cloned(),
kind,
known_kind: parsed.is_ok(),
platform: e
.annotations
.get(varve_core::platform::ANN_PLATFORM)
.cloned()
.unwrap_or_else(|| "any".to_string()),
digest: e.digest.clone(),
dispatch: match parsed {
Ok(k) if k.is_dispatchable() => DISPATCHED,
Ok(_) => HELD,
Err(_) => UNKNOWN,
},
present: store_of(l).entry_path(&l.entry, e).is_some(),
layer: l.entry.layer.to_string(),
realm: l.realm.clone(),
name,
});
}
}
rows.sort_by(|a, b| {
(&a.layer, &a.kind, &a.name, &a.version, &a.platform).cmp(&(
&b.layer,
&b.kind,
&b.name,
&b.version,
&b.platform,
))
});
if json {
print_json(&target, &layers, &rows, &host);
} else {
print_text(&target, &layers, &rows, &host);
}
Ok(())
}
fn store_of(l: &crate::ComposedLayer) -> &Store {
&l.store
}
fn print_json(
target: &crate::ExportTarget,
layers: &[crate::ComposedLayer],
rows: &[Row],
host: &str,
) {
let composition: Vec<_> = layers
.iter()
.map(|l| {
serde_json::json!({
"layer": l.entry.layer.to_string(),
"manifest_digest": l.entry.digest,
"realm": l.realm,
"root": l.entry.digest == target.entry.digest,
})
})
.collect();
let payloads: Vec<_> = rows
.iter()
.map(|r| {
serde_json::json!({
"name": r.name,
"version": r.version,
"kind": r.kind,
"known_kind": r.known_kind,
"platform": r.platform,
"dispatch": r.dispatch,
"digest": r.digest,
"present": r.present,
"layer": r.layer,
"realm": r.realm,
})
})
.collect();
let doc = serde_json::json!({
"command": "inspect",
"layer": target.entry.layer.to_string(),
"channel": target.entry.channel,
"manifest_digest": target.entry.digest,
"host_platform": host,
"composition": composition,
"payloads": payloads,
"summary": {
"payloads": rows.len(),
"dispatched": rows.iter().filter(|r| r.dispatch == DISPATCHED).count(),
"held": rows.iter().filter(|r| r.dispatch == HELD).count(),
"layers": layers.len(),
},
});
println!(
"{}",
serde_json::to_string_pretty(&doc).expect("the inspect report serialises")
);
}
fn print_text(
target: &crate::ExportTarget,
layers: &[crate::ComposedLayer],
rows: &[Row],
host: &str,
) {
println!(
"layer {} ({}) {}",
target.entry.layer, target.entry.channel, target.entry.digest
);
if layers.len() > 1 {
println!("composition: {} layers —", layers.len());
for l in layers {
println!(
" {} {} (verified against realm '{}'){}",
l.entry.layer,
l.entry.digest,
l.realm,
if l.entry.digest == target.entry.digest {
" [pinned]"
} else {
""
}
);
}
}
if rows.is_empty() {
println!("\nno payloads — this layer carries no entries beyond its composition");
return;
}
let dispatched = rows.iter().filter(|r| r.dispatch == DISPATCHED).count();
let held = rows.len() - dispatched;
println!(
"\n{} payload(s): {dispatched} DISPATCHED, {held} HELD (platform {host})\n",
rows.len()
);
let w = |f: fn(&Row) -> &str, head: &str| {
rows.iter()
.map(|r| f(r).chars().count())
.chain(std::iter::once(head.chars().count()))
.max()
.unwrap_or(1)
};
let (wn, wk, wp) = (w(|r| &r.name, "NAME"), w(|r| &r.kind, "KIND"), {
rows.iter()
.map(|r| r.platform.chars().count())
.chain(std::iter::once(8))
.max()
.unwrap_or(8)
});
let wv = rows
.iter()
.map(|r| r.version.as_deref().unwrap_or("-").chars().count())
.chain(std::iter::once(7))
.max()
.unwrap_or(7);
println!(
" {:<12}{:<wk$} {:<wn$} {:<wv$} {:<wp$} LAYER",
"", "KIND", "NAME", "VERSION", "PLATFORM"
);
for r in rows {
println!(
" {:<12}{:<wk$} {:<wn$} {:<wv$} {:<wp$} {}{}",
r.dispatch.to_uppercase(),
r.kind,
r.name,
r.version.as_deref().unwrap_or("-"),
r.platform,
r.layer,
if r.present {
""
} else {
" (not laid down here)"
},
);
}
if held > 0 {
println!(
"\nHELD payloads are stored and verified but NOT on your PATH — `varve which` will \
not find them, by design. Only a `tool` is dispatched by name. See \
`varve docs inspect`."
);
}
if rows.iter().any(|r| !r.known_kind) {
println!(
"\nAn entry above carries a payload kind this varve does not know. Its bytes still \
verify against the signed digest — only the adapters that must DO something \
kind-specific will refuse it. A newer varve may handle it."
);
}
}