use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfoReport {
pub info_schema_version: u32,
pub runner_version: String,
pub wasmtime_version: String,
pub target_triple: String,
pub build_profile: String,
pub build_timestamp_utc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_sha: Option<String>,
pub pack_format_versions: Vec<u32>,
pub features: Features,
pub wasi_imports: Vec<InterfaceBinding>,
pub greentic_imports: Vec<InterfaceBinding>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Features {
pub enabled: Vec<String>,
pub disabled: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InterfaceBinding {
pub interface: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub versions: Vec<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub opt_in_per_pack: bool,
}
fn is_false(b: &bool) -> bool {
!*b
}
pub const PACK_FORMAT_VERSIONS: &[u32] = &[greentic_pack::builder::PACK_VERSION];
pub const WASI_IMPORTS: &[(&str, &str)] = &[
("wasi:io", "0.2.0"),
("wasi:clocks", "0.2.0"),
("wasi:filesystem", "0.2.0"),
("wasi:random", "0.2.0"),
("wasi:sockets", "0.2.0"),
("wasi:http", "0.2.0"),
("wasi:tls", "0.2.0"),
];
pub struct GreenticImport {
pub interface: &'static str,
pub versions: &'static [&'static str],
pub opt_in_per_pack: bool,
}
pub const GREENTIC_IMPORTS: &[GreenticImport] = &[
GreenticImport {
interface: "greentic:component/control",
versions: &["0.4.0", "0.5.0"],
opt_in_per_pack: false,
},
GreenticImport {
interface: "greentic:http/client",
versions: &["1.0.0", "1.1.0"],
opt_in_per_pack: false,
},
GreenticImport {
interface: "greentic:interfaces/host",
versions: &["0.6.0"],
opt_in_per_pack: false,
},
GreenticImport {
interface: "greentic:interfaces/telemetry",
versions: &["0.6.0"],
opt_in_per_pack: false,
},
GreenticImport {
interface: "greentic:interfaces/state-store",
versions: &["0.6.0"],
opt_in_per_pack: true,
},
GreenticImport {
interface: "greentic:interfaces/secrets",
versions: &["1.1.0"],
opt_in_per_pack: false,
},
];
pub fn collect() -> InfoReport {
let mut enabled = Vec::new();
let mut disabled = Vec::new();
for (feat, on) in [
("verify", cfg!(feature = "verify")),
("telemetry", cfg!(feature = "telemetry")),
("session-redis", cfg!(feature = "session-redis")),
("fault-injection", cfg!(feature = "fault-injection")),
(
"component-v0-6-introspection",
cfg!(feature = "component-v0-6-introspection"),
),
] {
if on {
enabled.push(feat.to_string());
} else {
disabled.push(feat.to_string());
}
}
InfoReport {
info_schema_version: 1,
runner_version: env!("CARGO_PKG_VERSION").to_string(),
wasmtime_version: wasmtime_environ::VERSION.to_string(),
target_triple: env!("TARGET").to_string(),
build_profile: env!("BUILD_PROFILE").to_string(),
build_timestamp_utc: env!("BUILD_TIMESTAMP_UTC").to_string(),
git_sha: option_env!("GIT_SHA").map(String::from),
pack_format_versions: PACK_FORMAT_VERSIONS.to_vec(),
features: Features { enabled, disabled },
wasi_imports: WASI_IMPORTS
.iter()
.map(|(iface, ver)| InterfaceBinding {
interface: (*iface).to_string(),
version: Some((*ver).to_string()),
versions: Vec::new(),
opt_in_per_pack: false,
})
.collect(),
greentic_imports: GREENTIC_IMPORTS
.iter()
.map(|g| InterfaceBinding {
interface: g.interface.to_string(),
version: None,
versions: g.versions.iter().map(|v| (*v).to_string()).collect(),
opt_in_per_pack: g.opt_in_per_pack,
})
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_version_is_one() {
let r = collect();
assert_eq!(r.info_schema_version, 1);
}
#[test]
fn features_are_disjoint() {
let r = collect();
for f in &r.features.enabled {
assert!(
!r.features.disabled.contains(f),
"feature {f} appears in both enabled and disabled"
);
}
}
#[test]
fn runner_version_is_non_empty() {
let r = collect();
assert!(!r.runner_version.is_empty());
assert!(!r.wasmtime_version.is_empty());
}
#[test]
fn greentic_imports_listed_here_are_registered() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../greentic-runner-host/src/pack.rs"
);
let source = std::fs::read_to_string(path).expect("read pack.rs");
for g in GREENTIC_IMPORTS {
let found_explicit = g
.versions
.iter()
.any(|v| source.contains(&format!("{}@{}", g.interface, v)));
let fallback = match g.interface {
"greentic:interfaces/host" => {
source.contains("runner_host_http") || source.contains("runner_host_kv")
}
"greentic:interfaces/telemetry" => source.contains("telemetry_logger"),
"greentic:interfaces/state-store" => source.contains("state_store"),
"greentic:interfaces/secrets" => source.contains("secrets_store_v1_1"),
_ => false,
};
assert!(
found_explicit || fallback,
"GREENTIC_IMPORTS entry {} not referenced in register_all() source — \
either the interface was renamed/removed upstream (update this list) \
or the test is looking at the wrong file.",
g.interface,
);
}
}
}