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:
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")