verifyos_cli/rules/
xcode_requirements.rs1use crate::rules::core::{
2 AppStoreRule, ArtifactContext, RuleCategory, RuleError, RuleReport, RuleStatus, Severity,
3};
4
5pub struct XcodeVersionRule;
6
7impl AppStoreRule for XcodeVersionRule {
8 fn id(&self) -> &'static str {
9 "RULE_XCODE_26_MANDATE"
10 }
11
12 fn name(&self) -> &'static str {
13 "Xcode 26 / iOS 26 SDK Mandate"
14 }
15
16 fn category(&self) -> RuleCategory {
17 RuleCategory::Metadata
18 }
19
20 fn severity(&self) -> Severity {
21 Severity::Warning
22 }
23
24 fn recommendation(&self) -> &'static str {
25 "From April 2026, all apps must be built with Xcode 26 and the iOS 26 SDK."
26 }
27
28 fn evaluate(&self, artifact: &ArtifactContext) -> Result<RuleReport, RuleError> {
29 let Some(plist) = artifact.info_plist else {
30 return Ok(RuleReport {
31 status: RuleStatus::Skip,
32 message: Some("Info.plist not found".to_string()),
33 evidence: None,
34 });
35 };
36
37 let mut failures = Vec::new();
38
39 let dtxcode = plist.get_string("DTXcode");
41 if let Some(version_str) = dtxcode {
42 if let Ok(version_int) = version_str.parse::<u32>() {
43 if version_int < 1800 { if version_int < 2600 {
52 failures.push(format!("Built with Xcode version {} (required 2600+)", version_str));
53 }
54 }
55 }
56 } else {
57 failures.push("DTXcode key missing".to_string());
58 }
59
60 let platform_version = plist.get_string("DTPlatformVersion");
62 if let Some(version) = platform_version {
63 if let Ok(v) = version.parse::<f32>() {
64 if v < 26.0 {
65 failures.push(format!("Built with platform version {} (required 26.0+)", version));
66 }
67 }
68 }
69
70 let sdk_name = plist.get_string("DTSDKName");
72 if let Some(name) = sdk_name {
73 if !name.contains("26") {
74 failures.push(format!("Built with SDK {} (required iOS 26 SDK)", name));
75 }
76 }
77
78 if failures.is_empty() {
79 return Ok(RuleReport {
80 status: RuleStatus::Pass,
81 message: None,
82 evidence: None,
83 });
84 }
85
86 Ok(RuleReport {
87 status: RuleStatus::Fail,
88 message: Some("App does not meet 2026 build requirements".to_string()),
89 evidence: Some(failures.join("; ")),
90 })
91 }
92}