use std::collections::BTreeMap;
use super::model::PlatformManifest;
use crate::release::discovered::{CargoMetadata, DiscoveredCrate};
use crate::release::error::Diagnostic;
use crate::release::metadata::CrateMetadata;
use crate::release::version::Ybf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PlatformValidationReport {
pub(crate) ok: bool,
pub(crate) findings: Vec<Diagnostic>,
}
pub(crate) fn validate_platform(
manifest: &PlatformManifest,
document: &CargoMetadata,
crates: &[DiscoveredCrate],
) -> PlatformValidationReport {
let mut findings: Vec<Diagnostic> = Vec::new();
let publishable: BTreeMap<String, ()> = crates
.iter()
.filter_map(|c| match &c.metadata {
CrateMetadata::Valid(md) if md.publish => Some((c.name.clone(), ())),
_ => None,
})
.collect();
let current_versions: BTreeMap<String, Ybf> = document
.packages
.iter()
.filter_map(|p| {
p.version
.as_deref()
.and_then(|v| Ybf::parse(v).ok())
.map(|v| (p.name.clone(), v))
})
.collect();
for name in publishable.keys() {
if !manifest.crates.contains_key(name) {
findings.push(Diagnostic {
crate_name: name.clone(),
message: "publishable crate missing from Platform manifest".to_string(),
});
}
}
for name in manifest.crates.keys() {
if !publishable.contains_key(name) {
findings.push(Diagnostic {
crate_name: name.clone(),
message: "non-publishable or unknown crate in Platform manifest".to_string(),
});
}
}
let arcature = manifest.crates.get("arcature");
let arcature_dx = manifest.crates.get("arcature-dx");
if let (Some(a), Some(dx)) = (arcature, arcature_dx)
&& a != dx
{
findings.push(Diagnostic {
crate_name: "core".to_string(),
message: format!(
"core members have mismatched versions: arcature={a}, arcature-dx={dx} \
(invariant 3: core is atomic)"
),
});
}
for (name, manifest_version) in &manifest.crates {
if let Some(current) = current_versions.get(name)
&& manifest_version != current
{
findings.push(Diagnostic {
crate_name: name.clone(),
message: format!(
"manifest version {manifest_version} differs from workspace version {current} \
(certifies a different published set)"
),
});
}
}
findings.sort_by(|a, b| {
a.crate_name
.cmp(&b.crate_name)
.then(a.message.cmp(&b.message))
});
PlatformValidationReport {
ok: findings.is_empty(),
findings,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::release::discovered::{Package, Target};
use crate::release::metadata::ReleaseMetadata;
use crate::release::metadata::Role;
fn package(name: &str, version: &str, metadata: Option<&str>) -> Package {
Package {
name: name.to_string(),
version: Some(version.to_string()),
manifest_path: format!("/repo/crates/{name}/Cargo.toml"),
publish: None,
metadata: metadata.map(|m| serde_json::from_str(m).unwrap()),
targets: vec![Target {
kind: vec!["lib".to_string()],
}],
dependencies: Vec::new(),
}
}
fn discovered(name: &str, unit: &str) -> DiscoveredCrate {
DiscoveredCrate {
name: name.to_string(),
manifest_path: std::path::PathBuf::from(format!("/repo/crates/{name}/Cargo.toml")),
publishable: true,
proc_macro: false,
metadata: CrateMetadata::Valid(ReleaseMetadata {
publish: true,
role: Some(if unit == "core" {
Role::Facade
} else {
Role::Subsystem
}),
release_unit: Some(unit.to_string()),
}),
}
}
fn manifest(crates: &[(&str, &str)]) -> PlatformManifest {
PlatformManifest {
platform: super::super::model::PlatformVersion {
year: 2026,
break_: 1,
},
crates: crates
.iter()
.map(|(n, v)| (n.to_string(), Ybf::parse(v).unwrap()))
.collect(),
}
}
#[test]
fn valid_manifest_passes() {
let doc = CargoMetadata {
packages: vec![package(
"arcature-auth",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"subsystem","release-unit":"arcature-auth"}}"#,
),
)],
};
let crates = vec![discovered("arcature-auth", "arcature-auth")];
let m = manifest(&[("arcature-auth", "2026.1.0")]);
let report = validate_platform(&m, &doc, &crates);
assert!(report.ok);
assert!(report.findings.is_empty());
}
#[test]
fn missing_crate_is_finding() {
let doc = CargoMetadata {
packages: vec![
package(
"arcature-auth",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"subsystem","release-unit":"arcature-auth"}}"#,
),
),
package(
"arcature-db",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"subsystem","release-unit":"arcature-db"}}"#,
),
),
],
};
let crates = vec![
discovered("arcature-auth", "arcature-auth"),
discovered("arcature-db", "arcature-db"),
];
let m = manifest(&[("arcature-auth", "2026.1.0")]);
let report = validate_platform(&m, &doc, &crates);
assert!(!report.ok);
assert!(
report
.findings
.iter()
.any(|f| f.crate_name == "arcature-db" && f.message.contains("missing"))
);
}
#[test]
fn unknown_crate_is_finding() {
let doc = CargoMetadata {
packages: vec![package(
"arcature-auth",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"subsystem","release-unit":"arcature-auth"}}"#,
),
)],
};
let crates = vec![discovered("arcature-auth", "arcature-auth")];
let m = manifest(&[("arcature-auth", "2026.1.0"), ("unknown-crate", "2026.1.0")]);
let report = validate_platform(&m, &doc, &crates);
assert!(!report.ok);
assert!(
report
.findings
.iter()
.any(|f| f.crate_name == "unknown-crate" && f.message.contains("non-publishable"))
);
}
#[test]
fn core_mismatch_is_finding() {
let doc = CargoMetadata {
packages: vec![
package(
"arcature",
"2026.1.0",
Some(r#"{"arcature":{"publish":true,"role":"facade","release-unit":"core"}}"#),
),
package(
"arcature-dx",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"proc-macro","release-unit":"core"}}"#,
),
),
],
};
let crates = vec![
discovered("arcature", "core"),
discovered("arcature-dx", "core"),
];
let m = manifest(&[("arcature", "2026.1.0"), ("arcature-dx", "2026.1.1")]);
let report = validate_platform(&m, &doc, &crates);
assert!(!report.ok);
assert!(
report
.findings
.iter()
.any(|f| f.crate_name == "core" && f.message.contains("mismatched"))
);
}
#[test]
fn version_mismatch_is_finding() {
let doc = CargoMetadata {
packages: vec![package(
"arcature-auth",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"subsystem","release-unit":"arcature-auth"}}"#,
),
)],
};
let crates = vec![discovered("arcature-auth", "arcature-auth")];
let m = manifest(&[("arcature-auth", "2026.1.1")]);
let report = validate_platform(&m, &doc, &crates);
assert!(!report.ok);
assert!(
report
.findings
.iter()
.any(|f| f.crate_name == "arcature-auth" && f.message.contains("differs"))
);
}
#[test]
fn core_match_passes() {
let doc = CargoMetadata {
packages: vec![
package(
"arcature",
"2026.1.0",
Some(r#"{"arcature":{"publish":true,"role":"facade","release-unit":"core"}}"#),
),
package(
"arcature-dx",
"2026.1.0",
Some(
r#"{"arcature":{"publish":true,"role":"proc-macro","release-unit":"core"}}"#,
),
),
],
};
let crates = vec![
discovered("arcature", "core"),
discovered("arcature-dx", "core"),
];
let m = manifest(&[("arcature", "2026.1.0"), ("arcature-dx", "2026.1.0")]);
let report = validate_platform(&m, &doc, &crates);
assert!(report.ok);
}
}