use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use crate::paths::state::{
validate_machine_id, validate_pod_name, ResolvedCoordinationScope, StateLayout,
};
#[derive(Debug, Clone, Serialize)]
pub(crate) struct MachineRecord {
pub id: String,
pub display_name: Option<String>,
pub trust_class: MachineTrustClass,
pub profiles: Vec<String>,
pub capabilities: Vec<String>,
pub source: MachineSource,
pub pod_name: Option<String>,
pub manifest_path: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum MachineSource {
Flat,
PodScoped,
}
impl MachineSource {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Flat => "flat",
Self::PodScoped => "pod_scoped",
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
struct MachineManifestFile {
machine: MachineSection,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct MachineSection {
id: String,
display_name: Option<String>,
trust_class: MachineTrustClass,
profiles: Vec<String>,
capabilities: Vec<String>,
}
pub(crate) fn load_all_machines(ccd_root: &Path) -> Result<Vec<MachineRecord>> {
let mut seen: BTreeSet<String> = BTreeSet::new();
let mut records = Vec::new();
for record in load_flat_machines(ccd_root)? {
if seen.insert(record.id.clone()) {
records.push(record);
}
}
for record in load_pod_scoped_machines(ccd_root)? {
if seen.insert(record.id.clone()) {
records.push(record);
}
}
records.sort_by(|a, b| a.id.cmp(&b.id));
Ok(records)
}
fn load_flat_machines(ccd_root: &Path) -> Result<Vec<MachineRecord>> {
let root = ccd_root.join("machines");
let entries = read_dir_sorted(&root)?;
let mut records: Vec<MachineRecord> = Vec::new();
let mut seen: BTreeMap<String, PathBuf> = BTreeMap::new();
for entry in entries {
if !entry.file_type()?.is_dir() {
continue;
}
let manifest_path = entry.path().join("machine.toml");
if !manifest_path.is_file() {
continue;
}
let record = load_manifest(&manifest_path, MachineSource::Flat, None, None)?;
if let Some(existing) = seen.get(&record.id) {
bail!(
"duplicate machine id `{}` in flat layout: {} and {}",
record.id,
existing.display(),
manifest_path.display()
);
}
seen.insert(record.id.clone(), manifest_path.clone());
records.push(record);
}
Ok(records)
}
fn load_pod_scoped_machines(ccd_root: &Path) -> Result<Vec<MachineRecord>> {
let pods_root = ccd_root.join("pods");
let entries = read_dir_sorted(&pods_root)?;
let mut records: Vec<MachineRecord> = Vec::new();
let mut seen: BTreeMap<String, PathBuf> = BTreeMap::new();
for entry in entries {
if !entry.file_type()?.is_dir() {
continue;
}
let pod_dir = entry.path();
let Some(pod_name) = pod_dir
.file_name()
.and_then(|segment| segment.to_str())
.map(String::from)
else {
continue;
};
let manifest_path = pod_dir.join("machine.toml");
if !manifest_path.is_file() {
continue;
}
let Some(owned_profiles) = load_pod_owned_profiles(&pod_dir)? else {
continue;
};
let record = load_manifest(
&manifest_path,
MachineSource::PodScoped,
Some(pod_name.clone()),
Some(&owned_profiles),
)?;
if let Some(existing) = seen.get(&record.id) {
bail!(
"duplicate machine id `{}` in pod-scoped layout: {} and {}",
record.id,
existing.display(),
manifest_path.display()
);
}
seen.insert(record.id.clone(), manifest_path.clone());
records.push(record);
}
Ok(records)
}
fn load_manifest(
manifest_path: &Path,
source: MachineSource,
pod_name: Option<String>,
owned_profiles: Option<&BTreeSet<String>>,
) -> Result<MachineRecord> {
let raw = fs::read_to_string(manifest_path)
.with_context(|| format!("failed to read {}", manifest_path.display()))?;
let manifest: MachineManifestFile = toml::from_str(&raw)
.with_context(|| format!("failed to parse {}", manifest_path.display()))?;
if manifest.machine.id.trim().is_empty() {
bail!(
"{} must declare a non-empty machine.id",
manifest_path.display()
);
}
validate_machine_id(&manifest.machine.id)?;
for profile in &manifest.machine.profiles {
if profile.trim().is_empty() {
bail!(
"{} contains an empty profile entry in machine.profiles",
manifest_path.display()
);
}
if let Some(owned) = owned_profiles {
if !owned.contains(profile) {
bail!(
"{} declares machine profile `{profile}` that is not owned by pod `{}`",
manifest_path.display(),
pod_name.as_deref().unwrap_or("<unknown>")
);
}
}
}
Ok(MachineRecord {
id: manifest.machine.id,
display_name: manifest.machine.display_name,
trust_class: manifest.machine.trust_class,
profiles: manifest.machine.profiles,
capabilities: manifest.machine.capabilities,
source,
pod_name,
manifest_path: manifest_path.display().to_string(),
})
}
fn load_pod_owned_profiles(pod_dir: &Path) -> Result<Option<BTreeSet<String>>> {
let pod_toml = pod_dir.join("pod.toml");
if !pod_toml.is_file() {
return Ok(None);
}
let raw = fs::read_to_string(&pod_toml)
.with_context(|| format!("failed to read {}", pod_toml.display()))?;
let value: toml::Value =
toml::from_str(&raw).with_context(|| format!("failed to parse {}", pod_toml.display()))?;
value
.get("pod")
.and_then(|section| section.get("name"))
.and_then(|name| name.as_str())
.ok_or_else(|| anyhow::anyhow!("{} must declare [pod].name", pod_toml.display()))?;
let owned: BTreeSet<String> = value
.get("profiles")
.and_then(|section| section.as_table())
.map(|table| table.keys().cloned().collect())
.unwrap_or_default();
Ok(Some(owned))
}
fn read_dir_sorted(root: &Path) -> Result<Vec<fs::DirEntry>> {
let Ok(iter) = fs::read_dir(root) else {
return Ok(Vec::new());
};
let mut entries: Vec<fs::DirEntry> = iter.collect::<std::io::Result<Vec<_>>>()?;
entries.sort_by_key(|entry| entry.file_name());
Ok(entries)
}
#[derive(Debug, Clone)]
pub(crate) struct LoadedPodIdentity {
pub name: String,
pub manifest_path: PathBuf,
pub policy_path: PathBuf,
pub owned_profiles: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct CoordinationScopeView {
pub status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shared_root: Option<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct LoadedMachineIdentity {
pub id: String,
pub display_name: Option<String>,
pub manifest_path: PathBuf,
pub trust_class: MachineTrustClass,
pub available_profiles: Vec<String>,
pub capabilities: Vec<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum MachineTrustClass {
#[default]
Owned,
Limited,
Observer,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct MachineIdentityView {
pub status: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manifest_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trust_class: Option<MachineTrustClass>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub available_profiles: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
struct PodManifestFile {
pod: PodSection,
#[serde(default)]
profiles: BTreeMap<String, PodProfileSection>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
struct PodSection {
name: String,
#[serde(default)]
#[allow(dead_code)]
display_name: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct PodProfileSection {
description: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
struct PodScopedMachineManifestFile {
machine: PodScopedMachineSection,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct PodScopedMachineSection {
id: String,
display_name: Option<String>,
trust_class: MachineTrustClass,
profiles: Vec<String>,
capabilities: Vec<String>,
}
pub(crate) fn resolve_active_identity(layout: &StateLayout) -> Result<Option<LoadedPodIdentity>> {
let profile_name = layout.profile().as_str();
let mut matches = Vec::new();
for manifest in load_all_manifests(layout)? {
if manifest
.owned_profiles
.iter()
.any(|owned| owned == profile_name)
{
matches.push(manifest);
}
}
if matches.len() > 1 {
let owners = matches
.iter()
.map(|identity| identity.name.clone())
.collect::<Vec<_>>()
.join(", ");
bail!(
"profile `{profile_name}` is claimed by multiple pod identities ({owners}); edit the conflicting `pod.toml` files so one profile belongs to only one pod"
);
}
Ok(matches.into_iter().next())
}
pub(crate) fn resolve_coordination_scope_view(
layout: &StateLayout,
locality_id: &str,
) -> Result<CoordinationScopeView> {
let resolved = layout.resolved_coordination_scope(locality_id)?;
Ok(coordination_scope_view_from(resolved.as_ref()))
}
pub(crate) fn resolve_active_machine_identity(
layout: &StateLayout,
) -> Result<Option<LoadedMachineIdentity>> {
let Some(identity) = resolve_active_identity(layout)? else {
return Ok(None);
};
let manifest_path = layout.pod_machine_manifest_path(&identity.name)?;
if !manifest_path.is_file() {
return Ok(None);
}
Ok(Some(load_pod_machine_manifest(&identity, &manifest_path)?))
}
pub(crate) fn resolve_machine_identity_view(layout: &StateLayout) -> Result<MachineIdentityView> {
let active_pod = resolve_active_identity(layout)?;
let active_machine = active_pod
.as_ref()
.map(|_| resolve_active_machine_identity(layout))
.transpose()?
.flatten();
machine_identity_view_from(active_pod.as_ref(), active_machine.as_ref(), layout)
}
fn load_all_manifests(layout: &StateLayout) -> Result<Vec<LoadedPodIdentity>> {
let pods_root = layout.ccd_root().join("pods");
let Ok(entries) = fs::read_dir(&pods_root) else {
return Ok(Vec::new());
};
let mut manifests = Vec::new();
for entry in entries {
let entry = entry?;
if !entry.file_type()?.is_dir() {
continue;
}
let pod_dir = entry.path();
let manifest_path = pod_dir.join("pod.toml");
if !manifest_path.is_file() {
continue;
}
manifests.push(load_pod_manifest(&pod_dir, &manifest_path)?);
}
Ok(manifests)
}
fn load_pod_manifest(pod_dir: &Path, manifest_path: &Path) -> Result<LoadedPodIdentity> {
let raw = fs::read_to_string(manifest_path)
.with_context(|| format!("failed to read {}", manifest_path.display()))?;
let manifest: PodManifestFile = toml::from_str(&raw)
.with_context(|| format!("failed to parse {}", manifest_path.display()))?;
if manifest.profiles.is_empty() {
bail!(
"{} must declare at least one owned profile under [profiles]",
manifest_path.display()
);
}
let dir_name = pod_dir
.file_name()
.and_then(|segment| segment.to_str())
.ok_or_else(|| anyhow::anyhow!("invalid pod directory name for {}", pod_dir.display()))?;
validate_pod_name(&manifest.pod.name)?;
if manifest.pod.name != dir_name {
bail!(
"{} declares pod name `{}` but lives under `pods/{dir_name}`; keep the manifest name aligned with its directory",
manifest_path.display(),
manifest.pod.name
);
}
let owned_profiles = manifest
.profiles
.into_keys()
.map(|name| {
if name.trim().is_empty() {
bail!(
"{} contains an empty profile key under [profiles]",
manifest_path.display()
);
}
Ok(name)
})
.collect::<Result<Vec<_>>>()?;
let policy_path = pod_dir.join("policy.md");
Ok(LoadedPodIdentity {
name: manifest.pod.name,
manifest_path: manifest_path.to_path_buf(),
policy_path,
owned_profiles,
})
}
fn load_pod_machine_manifest(
pod_identity: &LoadedPodIdentity,
manifest_path: &Path,
) -> Result<LoadedMachineIdentity> {
let raw = fs::read_to_string(manifest_path)
.with_context(|| format!("failed to read {}", manifest_path.display()))?;
let manifest: PodScopedMachineManifestFile = toml::from_str(&raw)
.with_context(|| format!("failed to parse {}", manifest_path.display()))?;
crate::paths::state::validate_machine_id(&manifest.machine.id)?;
if manifest.machine.id.trim().is_empty() {
bail!(
"{} must declare a non-empty machine.id",
manifest_path.display()
);
}
for profile in &manifest.machine.profiles {
if profile.trim().is_empty() {
bail!(
"{} contains an empty profile entry in machine.profiles",
manifest_path.display()
);
}
if !pod_identity
.owned_profiles
.iter()
.any(|owned| owned == profile)
{
bail!(
"{} declares machine profile `{profile}` that is not owned by pod `{}`",
manifest_path.display(),
pod_identity.name
);
}
}
Ok(LoadedMachineIdentity {
id: manifest.machine.id,
display_name: manifest.machine.display_name,
manifest_path: manifest_path.to_path_buf(),
trust_class: manifest.machine.trust_class,
available_profiles: manifest.machine.profiles,
capabilities: manifest.machine.capabilities,
})
}
fn machine_identity_view_from(
pod_identity: Option<&LoadedPodIdentity>,
machine_identity: Option<&LoadedMachineIdentity>,
layout: &StateLayout,
) -> Result<MachineIdentityView> {
match machine_identity {
Some(identity) => Ok(MachineIdentityView {
status: "declared",
id: Some(identity.id.clone()),
display_name: identity.display_name.clone(),
manifest_path: Some(identity.manifest_path.display().to_string()),
trust_class: Some(identity.trust_class),
available_profiles: identity.available_profiles.clone(),
capabilities: identity.capabilities.clone(),
reason: None,
}),
None => Ok(MachineIdentityView {
status: "missing",
id: None,
display_name: None,
manifest_path: pod_identity
.map(|identity| layout.pod_machine_manifest_path(&identity.name))
.transpose()?
.map(|path| path.display().to_string()),
trust_class: None,
available_profiles: Vec::new(),
capabilities: Vec::new(),
reason: pod_identity.map(|identity| {
format!(
"no machine identity is declared yet for pod `{}`",
identity.name
)
}),
}),
}
}
fn coordination_scope_view_from(
scope: Option<&ResolvedCoordinationScope>,
) -> CoordinationScopeView {
match scope {
Some(scope) => CoordinationScopeView {
status: "configured",
name: Some(scope.name.clone()),
source: Some(scope.source.as_str()),
config_path: Some(scope.config_path.display().to_string()),
shared_root: Some(scope.shared_root.display().to_string()),
},
None => CoordinationScopeView {
status: "missing",
name: None,
source: None,
config_path: None,
shared_root: None,
},
}
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::tempdir;
use super::*;
fn write(path: &std::path::Path, body: &str) {
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, body).unwrap();
}
fn operator_pod_toml() -> &'static str {
r#"[pod]
name = "operator"
[profiles]
main = { description = "Primary profile" }
"#
}
const FLAT_MANIFEST: &str = r#"[machine]
id = "laptop-amsterdam"
display_name = "Operator Laptop"
trust_class = "owned"
profiles = ["main"]
capabilities = ["git", "cargo"]
"#;
const POD_MANIFEST: &str = r#"[machine]
id = "desktop-tokyo"
display_name = "Operator Desktop"
trust_class = "limited"
profiles = ["main"]
capabilities = ["docker"]
"#;
#[test]
fn returns_empty_when_no_layout_exists() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
let records = load_all_machines(&ccd_root).unwrap();
assert!(records.is_empty());
}
#[test]
fn reads_flat_layout_only() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/laptop-amsterdam/machine.toml"),
FLAT_MANIFEST,
);
let records = load_all_machines(&ccd_root).unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].id, "laptop-amsterdam");
assert_eq!(records[0].source, MachineSource::Flat);
assert!(records[0].pod_name.is_none());
}
#[test]
fn reads_pod_scoped_layout_only() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("pods/operator/pod.toml"),
operator_pod_toml(),
);
write(&ccd_root.join("pods/operator/machine.toml"), POD_MANIFEST);
let records = load_all_machines(&ccd_root).unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].id, "desktop-tokyo");
assert_eq!(records[0].source, MachineSource::PodScoped);
assert_eq!(records[0].pod_name.as_deref(), Some("operator"));
}
#[test]
fn merges_both_layouts_sorted_by_id() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/laptop-amsterdam/machine.toml"),
FLAT_MANIFEST,
);
write(
&ccd_root.join("pods/operator/pod.toml"),
operator_pod_toml(),
);
write(&ccd_root.join("pods/operator/machine.toml"), POD_MANIFEST);
let records = load_all_machines(&ccd_root).unwrap();
assert_eq!(records.len(), 2);
assert_eq!(records[0].id, "desktop-tokyo");
assert_eq!(records[0].source, MachineSource::PodScoped);
assert_eq!(records[1].id, "laptop-amsterdam");
assert_eq!(records[1].source, MachineSource::Flat);
}
#[test]
fn flat_layout_wins_on_cross_layout_id_collision() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/shared/machine.toml"),
r#"[machine]
id = "shared"
trust_class = "owned"
profiles = ["main"]
"#,
);
write(
&ccd_root.join("pods/operator/pod.toml"),
operator_pod_toml(),
);
write(
&ccd_root.join("pods/operator/machine.toml"),
r#"[machine]
id = "shared"
trust_class = "limited"
profiles = ["main"]
"#,
);
let records = load_all_machines(&ccd_root).unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].source, MachineSource::Flat);
assert_eq!(records[0].trust_class, MachineTrustClass::Owned);
}
#[test]
fn skips_presence_subdir_in_flat_layout() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/laptop-amsterdam/machine.toml"),
FLAT_MANIFEST,
);
write(
&ccd_root.join("machines/presence/laptop-amsterdam.json"),
"{}",
);
let records = load_all_machines(&ccd_root).unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].id, "laptop-amsterdam");
}
#[test]
fn skips_pod_dir_without_pod_toml() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(&ccd_root.join("pods/ghost/machine.toml"), POD_MANIFEST);
let records = load_all_machines(&ccd_root).unwrap();
assert!(
records.is_empty(),
"pod dir without pod.toml must not surface machine records: {:?}",
records
);
}
#[test]
fn rejects_empty_machine_id() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/bad/machine.toml"),
r#"[machine]
id = ""
trust_class = "owned"
profiles = ["main"]
"#,
);
let err = load_all_machines(&ccd_root).expect_err("empty id must fail");
let msg = format!("{err:#}");
assert!(
msg.contains("non-empty machine.id"),
"expected non-empty machine.id error, got: {msg}"
);
}
#[test]
fn rejects_whitespace_only_machine_id() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/bad/machine.toml"),
"[machine]\nid = \" \"\ntrust_class = \"owned\"\nprofiles = [\"main\"]\n",
);
let err = load_all_machines(&ccd_root).expect_err("whitespace id must fail");
assert!(format!("{err:#}").contains("non-empty machine.id"));
}
#[test]
fn rejects_empty_profile_entry() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/laptop/machine.toml"),
r#"[machine]
id = "laptop"
trust_class = "owned"
profiles = ["main", ""]
"#,
);
let err = load_all_machines(&ccd_root).expect_err("empty profile entry must fail");
assert!(format!("{err:#}").contains("empty profile entry"));
}
#[test]
fn rejects_duplicate_flat_layout_ids() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("machines/alpha/machine.toml"),
r#"[machine]
id = "same"
trust_class = "owned"
profiles = ["main"]
"#,
);
write(
&ccd_root.join("machines/beta/machine.toml"),
r#"[machine]
id = "same"
trust_class = "owned"
profiles = ["main"]
"#,
);
let err = load_all_machines(&ccd_root).expect_err("duplicate flat id must fail");
let msg = format!("{err:#}");
assert!(msg.contains("duplicate machine id `same`"));
assert!(msg.contains("machines/alpha/machine.toml"));
assert!(msg.contains("machines/beta/machine.toml"));
}
#[test]
fn rejects_duplicate_pod_scoped_ids() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(&ccd_root.join("pods/podA/pod.toml"), operator_pod_toml());
write(&ccd_root.join("pods/podB/pod.toml"), operator_pod_toml());
write(
&ccd_root.join("pods/podA/machine.toml"),
r#"[machine]
id = "same"
trust_class = "owned"
profiles = ["main"]
"#,
);
write(
&ccd_root.join("pods/podB/machine.toml"),
r#"[machine]
id = "same"
trust_class = "limited"
profiles = ["main"]
"#,
);
let err = load_all_machines(&ccd_root).expect_err("duplicate pod id must fail");
let msg = format!("{err:#}");
assert!(msg.contains("duplicate machine id `same`"));
assert!(msg.contains("pods/podA/machine.toml"));
assert!(msg.contains("pods/podB/machine.toml"));
}
#[test]
fn rejects_pod_scoped_profile_not_owned_by_pod() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("pods/operator/pod.toml"),
operator_pod_toml(),
);
write(
&ccd_root.join("pods/operator/machine.toml"),
r#"[machine]
id = "laptop"
trust_class = "owned"
profiles = ["not-owned"]
"#,
);
let err = load_all_machines(&ccd_root).expect_err("unowned profile must fail");
let msg = format!("{err:#}");
assert!(msg.contains("not owned by pod `operator`"), "got: {msg}");
}
#[test]
fn rejects_invalid_pod_toml() {
let temp = tempdir().unwrap();
let ccd_root = temp.path().join(".ccd");
write(
&ccd_root.join("pods/broken/pod.toml"),
"this is not toml ===",
);
write(&ccd_root.join("pods/broken/machine.toml"), POD_MANIFEST);
let err = load_all_machines(&ccd_root).expect_err("parse error must fail");
assert!(format!("{err:#}").contains("failed to parse"));
}
}