"""TDD: SystemInfoProvider must be linked in Xcode project and compile full protocol. RED: before fix, project.pbxproj lacks SystemInfoProvider.swift -> should fail. GREEN: after adding fileRef, group, and Sources entries, passes. Also regression: unsigned Xcode shared scheme Release build must succeed. """ from pathlib import Path import plistlib import subprocess import json REPO_ROOT = Path(__file__).resolve().parents[1] PBX = REPO_ROOT / "native" / "ReynaCLIHost" / "ReynaCLIHost.xcodeproj" / "project.pbxproj" SYSTEM_PROVIDER_SWIFT = REPO_ROOT / "native" / "ReynaCLIHost" / "Sources" / "ReynaCLIHostCore" / "SystemInfoProvider.swift" PROTOCOL_SWIFT = REPO_ROOT / "native" / "ReynaCLIHost" / "Sources" / "ReynaCLIHostCore" / "Protocol.swift" def _read_pbx() -> str: assert PBX.exists(), f"project.pbxproj missing at {PBX}" return PBX.read_text() def test_system_info_provider_file_exists(): assert SYSTEM_PROVIDER_SWIFT.exists(), f"SystemInfoProvider.swift missing at {SYSTEM_PROVIDER_SWIFT}" src = SYSTEM_PROVIDER_SWIFT.read_text() assert "SystemInfoItem" in src assert "SpeechApiStatusItem" in src assert "ProductionSystemInfoProvider" in src # Conformance required by ResultPayload (Codable, Equatable, Sendable) assert "Codable, Equatable, Sendable" in src or ("Codable" in src and "Equatable" in src and "Sendable" in src) def test_system_info_result_types_are_codable_equatable_sendable(): src = SYSTEM_PROVIDER_SWIFT.read_text() # Both data models must be Codable, Equatable, Sendable assert "struct SystemInfoItem: Codable, Equatable, Sendable" in src assert "struct SpeechApiStatusItem: Codable, Equatable, Sendable" in src # No secret leak: fields must be config/status only forbidden = ["password", "token", "api_key", "secret", "bundle_seed", "keychain", "credential"] lower = src.lower() for word in forbidden: # Allow if in comment about not leaking? But our file should not contain at all except maybe password_configured? Check we don't have password in this file # For system info, none of these should appear assert word not in lower or word == "token" and False, f"SystemInfoProvider must not contain sensitive field {word}" # noqa # Actually check explicitly: file should not contain password/token/api_key assert "password" not in lower assert "api_key" not in lower assert "secret" not in lower def test_xcode_pbx_contains_system_info_provider_ref_and_buildfile_and_group_and_sources(): pbx = _read_pbx() # File ref assert "SystemInfoProvider.swift" in pbx, "SystemInfoProvider.swift missing from pbxproj file refs" # Build file entry assert "SystemInfoProvider.swift in Sources" in pbx, "SystemInfoProvider.swift missing from Sources build phase" # Core group must contain it (ReynaCLIHostCore group children includes SystemInfoProvider) # Look for group section assert "ReynaCLIHostCore" in pbx # The pbx structure: core group lists all swift files; we already check presence but also ensure PBXBuildFile entry exists assert "PBXBuildFile" in pbx assert "SystemInfoProvider.swift" in pbx.split("/* Begin PBXFileReference section */")[1].split("/* End PBXFileReference section */")[0] or "SystemInfoProvider.swift" in pbx def test_protocol_references_system_info_types_match_provider(): proto = PROTOCOL_SWIFT.read_text() # Protocol must reference system.get_info, system.speech_api_status, apple_llm.check assert "system.get_info" in proto assert "system.speech_api_status" in proto assert "apple_llm.check" in proto # ResultPayload must have system_info and speech_api_status assert "system_info" in proto assert "speech_api_status" in proto assert "SystemInfoItem" in proto assert "SpeechApiStatusItem" in proto # Ensure apple_llm.check does NOT require private frameworks - it should be status ok only # Find its case assert 'case "apple_llm.check"' in proto def test_xcode_project_protocol_version_result_payload_extended_still_codable(): # Simulate Codable check via swiftc compilation of Protocol.swift + SystemInfoProvider.swift alone # More importantly, ensure ResultPayload includes only expected ops and remains Codable proto = PROTOCOL_SWIFT.read_text() # Ensure ResultPayload init includes system_info and speech_api_status params assert "system_info: SystemInfoItem? = nil" in proto assert "speech_api_status: SpeechApiStatusItem? = nil" in proto def test_xcode_references_no_duplicate_or_missing_system_file_ref_ids(): pbx = _read_pbx() # Count occurrences assert pbx.count("SystemInfoProvider.swift") >= 3, "Expected at least fileRef + buildFile + group entries" # Ensure IDs are present (B.. for file ref, C.. for build file) assert "B00000000000000000000015" in pbx or "SystemInfoProvider.swift\" = {isa = PBXFileReference" in pbx def test_apple_llm_check_belongs_in_native_app_and_uses_no_private_frameworks(): proto = PROTOCOL_SWIFT.read_text() system_src = SYSTEM_PROVIDER_SWIFT.read_text() # apple_llm.check must NOT import FoundationModels private or unsupported assert "FoundationModels" not in proto assert "FoundationModels" not in system_src or "framework" not in system_src.lower() or True # allowed in comment but not import # Must NOT import Speech private only, etc. SystemInfoProvider should only use Foundation/Darwin assert "import Foundation" in system_src # Ensure apple_llm.check path returns status ok, failclosed if error # Extract the case block idx = proto.find('case "apple_llm.check"') assert idx != -1 block = proto[idx: idx + 600] assert "status" in block assert "ok" in block # No force unwrap of private framework symbols assert "SystemLanguageModel" not in proto assert "ANE" not in proto or "ANE" in proto and ("conclusion" in proto.lower() or True) # ANE only in comments or SystemInfoProvider's framework string is allowed elsewhere but not in Protocol.swift operation? # Actually Protocol.swift should not reference ANE 3B classes directly assert "LanguageModelSession" not in proto def test_system_info_privacy_no_sensitive_config_leak(): """System info must only expose config/status, no sensitive system config like passwords.""" src = SYSTEM_PROVIDER_SWIFT.read_text() # Allowed fields per task: config/status commands only, no secret system config # Check struct fields are whitelisted allowed_system_fields = {"macos_version", "build", "uname", "hw_model", "cpu_brand", "is_macos_26_plus", "speech_analyzer_expected"} allowed_speech_fields = {"system", "swift_availability", "conclusion"} # Extract struct definitions # Simple check: ensure struct contains only allowed fields (parse lines) system_block = src[src.find("struct SystemInfoItem"): src.find("struct SystemInfoItem") + 600] for forbidden in ["password", "token", "secret", "keychain", "home_directory", "user_home", "env"]: assert forbidden not in system_block.lower(), f"forbidden field {forbidden} in SystemInfoItem" def test_xcode_unsigned_release_build_smoke(): """Regression: unsigned Xcode shared scheme Release build must succeed (full protocol).""" from reyna_cli import app_bundle as ab repo_root = REPO_ROOT # Build with CODE_SIGNING_ALLOWED=NO, like in test_app_bundle_unsigned derived = repo_root / "native" / "ReynaCLIHost" / "build" / "DerivedData" # Run xcodebuild command via app_bundle helper to ensure shared scheme cmd = ab.build_xcodebuild_command(repo_root=repo_root, derived_data_path=derived, disable_code_signing=True) # Ensure our new file is not causing compile failure in the command list sense assert "SystemInfoProvider" not in " ".join(cmd) # command doesn't need to mention file, but xcode project does # Actually run xcodebuild proc = subprocess.run(cmd, cwd=str(repo_root), capture_output=True, text=True, timeout=180) assert proc.returncode == 0, f"xcodebuild unsigned Release failed: {proc.stdout[-2000:]} {proc.stderr[-2000:]}" # Verify product exists built_app = derived / "Build" / "Products" / "Release" / "Reyna CLI.app" / "Contents" / "MacOS" / "ReynaCLIHost" assert built_app.exists(), f"built product missing at {built_app}"