field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit 16e1fbbe669c662ca82ac95de98df2fc6728159f
parent 3aa823bc97725a43894095ccee5327fca6144831
Author: triesap <tyson@radroots.org>
Date:   Sat, 29 Aug 2026 03:08:50 +0000

test: bind persona evidence to xcresult

- Read simulator architecture from the exact XCUITest result bundle.
- Cross-check device identity, runtime, platform, and pass totals.
- Reject mismatched or non-arm64 development evidence.
- Cover provenance drift and preserve canonical persona verification.

Diffstat:
Mscripts/local-social-fixture.py | 51+++++++++++++++++++++++++++++++++++++++++++--------
Mscripts/test_local_social_fixture.py | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+), 8 deletions(-)

diff --git a/scripts/local-social-fixture.py b/scripts/local-social-fixture.py @@ -1054,7 +1054,7 @@ def directory_digest(path: Path) -> str: return digest.hexdigest() -def simulator_metadata(udid: str) -> dict[str, str]: +def simulator_metadata(udid: str, result_bundle: Path) -> dict[str, str]: devices = json.loads( subprocess.check_output(["xcrun", "simctl", "list", "devices", "--json"]) ) @@ -1066,12 +1066,47 @@ def simulator_metadata(udid: str) -> dict[str, str]: ] if len(matches) != 1: raise ValueError("simulator identity is unavailable") - runtime, _ = matches[0] - version = runtime.rsplit("iOS-", 1)[-1].replace("-", ".") - architecture = subprocess.check_output( - ["xcrun", "simctl", "spawn", udid, "uname", "-m"], text=True - ).strip() - if architecture != "arm64" or int(version.split(".")[0]) < 18: + runtime, simulator = matches[0] + runtime_version = runtime.rsplit("iOS-", 1)[-1].replace("-", ".") + summary = json.loads( + subprocess.check_output( + [ + "xcrun", + "xcresulttool", + "get", + "test-results", + "summary", + "--path", + str(result_bundle), + ] + ) + ) + result_devices = [ + row.get("device") + for row in summary.get("devicesAndConfigurations", []) + if isinstance(row, dict) + and isinstance(row.get("device"), dict) + and isinstance(row["device"].get("deviceId"), str) + and row["device"]["deviceId"].upper() == udid.upper() + ] + if len(result_devices) != 1: + raise ValueError("result bundle simulator identity is unavailable") + result_device = result_devices[0] + version = result_device.get("osVersion") + architecture = result_device.get("architecture") + if ( + summary.get("result") != "Passed" + or summary.get("failedTests") != 0 + or summary.get("passedTests") != 1 + or summary.get("skippedTests") != 0 + or simulator.get("isAvailable") is not True + or result_device.get("platform") != "iOS Simulator" + or version != runtime_version + or not isinstance(version, str) + or re.fullmatch(r"[1-9][0-9]*(\.[0-9]+){1,2}", version) is None + or int(version.split(".", 1)[0]) < 18 + or architecture != "arm64" + ): raise ValueError("simulator does not satisfy the development platform contract") return {"udid": udid.upper(), "os": f"iOS {version}", "architecture": architecture} @@ -1367,7 +1402,7 @@ def verify_persona(arguments: argparse.Namespace) -> int: "fixture_sha256": hashlib.sha256(fixture_raw).hexdigest(), "fixture_schema_sha256": hashlib.sha256(fixture_schema_raw).hexdigest(), "result_schema_sha256": hashlib.sha256(result_schema_raw).hexdigest(), - "simulator": simulator_metadata(arguments.simulator_id), + "simulator": simulator_metadata(arguments.simulator_id, result_bundle), "result_bundle_sha256": directory_digest(result_bundle), "evidence_sha256": hashlib.sha256(evidence_raw).hexdigest(), "personas": [ diff --git a/scripts/test_local_social_fixture.py b/scripts/test_local_social_fixture.py @@ -253,6 +253,78 @@ class LocalSocialFixtureTests(unittest.TestCase): changed, suite, "3" * 64, "4" * 64, "5" * 64 ) + def test_simulator_metadata_uses_exact_result_bundle_device(self) -> None: + udid = "11111111-2222-3333-4444-555555555555" + result_bundle = Path("result.xcresult") + simctl_devices = { + "devices": { + "com.apple.CoreSimulator.SimRuntime.iOS-26-5": [ + { + "udid": udid, + "isAvailable": True, + "name": "iPhone 17 Pro", + } + ] + } + } + result_summary = { + "failedTests": 0, + "passedTests": 1, + "result": "Passed", + "skippedTests": 0, + "devicesAndConfigurations": [ + { + "device": { + "architecture": "arm64", + "deviceId": udid, + "osVersion": "26.5", + "platform": "iOS Simulator", + } + } + ], + } + + with mock.patch.object( + fixture.subprocess, + "check_output", + side_effect=[json.dumps(simctl_devices), json.dumps(result_summary)], + ) as check_output: + self.assertEqual( + fixture.simulator_metadata(udid.lower(), result_bundle), + {"udid": udid, "os": "iOS 26.5", "architecture": "arm64"}, + ) + commands = [call.args[0] for call in check_output.call_args_list] + self.assertEqual(commands[0][:3], ["xcrun", "simctl", "list"]) + self.assertEqual(commands[1][:3], ["xcrun", "xcresulttool", "get"]) + self.assertNotIn("spawn", commands[0] + commands[1]) + + for mutation in ( + lambda value: value["devicesAndConfigurations"][0]["device"].update( + {"architecture": "x86_64"} + ), + lambda value: value["devicesAndConfigurations"][0]["device"].update( + {"deviceId": "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"} + ), + lambda value: value["devicesAndConfigurations"][0]["device"].update( + {"osVersion": "17.7"} + ), + lambda value: value["devicesAndConfigurations"][0]["device"].update( + {"platform": "macOS"} + ), + lambda value: value.update({"result": "Failed", "failedTests": 1}), + ): + changed = copy.deepcopy(result_summary) + mutation(changed) + with ( + mock.patch.object( + fixture.subprocess, + "check_output", + side_effect=[json.dumps(simctl_devices), json.dumps(changed)], + ), + self.assertRaises(ValueError), + ): + fixture.simulator_metadata(udid, result_bundle) + def test_control_is_bounded_and_deny_unknown(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory, "control.json")