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 {
44 if version_int < 2600 {
53 failures.push(format!(
54 "Built with Xcode version {} (required 2600+)",
55 version_str
56 ));
57 }
58 }
59 }
60 } else {
61 failures.push("DTXcode key missing".to_string());
62 }
63
64 let platform_version = plist.get_string("DTPlatformVersion");
66 if let Some(version) = platform_version {
67 if let Ok(v) = version.parse::<f32>() {
68 if v < 26.0 {
69 failures.push(format!(
70 "Built with platform version {} (required 26.0+)",
71 version
72 ));
73 }
74 }
75 }
76
77 let sdk_name = plist.get_string("DTSDKName");
79 if let Some(name) = sdk_name {
80 if !name.contains("26") {
81 failures.push(format!("Built with SDK {} (required iOS 26 SDK)", name));
82 }
83 }
84
85 if failures.is_empty() {
86 return Ok(RuleReport {
87 status: RuleStatus::Pass,
88 message: None,
89 evidence: None,
90 });
91 }
92
93 Ok(RuleReport {
94 status: RuleStatus::Fail,
95 message: Some("App does not meet 2026 build requirements".to_string()),
96 evidence: Some(failures.join("; ")),
97 })
98 }
99}