diff --git a/docs/ci-runners.md b/docs/ci-runners.md index fcd7652..e5b4691 100644 --- a/docs/ci-runners.md +++ b/docs/ci-runners.md @@ -46,7 +46,17 @@ Before the first JavaScript action, an inline shell bootstrap installs verified native Node 22.22.1 into a versioned runner tool cache and exports its bin directory through `GITHUB_PATH`. It needs no Homebrew/root access and remains available to action post-cleanup. This addresses run #4's `Cannot find: node in PATH` failure; -the modified workflow itself still needs a real Mac run. +native bootstrap and checkout were verified in Mac job 1062 (run 661/#6). +That job subsequently stalled in Apple-tool preflight after printing Xcode 27.1. +The preflight now prints each tool probe and bounds it to 60 seconds using Python +(`scripts/check-apple-tools.py`), so the next run identifies the unresponsive +command rather than hanging silently. This does not validate the iOS app. + +If an Apple probe stalls, run the named command in the runner user's interactive +login session, check the selected full Xcode, finish its first-launch components +and confirm CoreSimulator/runtimes work for that user. CI does not accept licenses, +change `xcode-select`, install runtimes or kill host simulator services. The runner +administrator must repair those prerequisites before native Apple validation. XcodeGen's pinned, checksum-verified binary includes native arm64 and x86_64 slices; Rosetta/Homebrew installation is not required by the workflow. Build, diff --git a/docs/continuation.md b/docs/continuation.md index 11c6301..5e6517d 100644 --- a/docs/continuation.md +++ b/docs/continuation.md @@ -42,7 +42,14 @@ collection before uninstall; the PNG was opened and confirmed nonblank. Five new output-regression tests guard missing, invalid, ambiguous and stale screenshots. See `validation.md` for logs and limitations. No Apple SDK result is implied. -The next Mac/Ubuntu workflow execution still needs inspection after pushing. +Android build job 1059 in global run 660 (#5) succeeded; the actual native debug +artifact was downloaded, checked and installed/launched on `emulator-5554`. +Jobs 1060/1061 still fail KVM preflight. Mac job 1062 in run 661 (#6) passed native +Node bootstrap and checkout, printed Xcode 27.1, then stalled in Apple-tool +preflight for at least 20 minutes. The new `check-apple-tools.py` prints each +command and imposes a 60-second deadline; fourteen runner/probe regressions pass. +Inspect its next Mac run for the exact stalled command and fix the runner user +session/Xcode/CoreSimulator prerequisites without weakening the check. Rebuild/collect from a clean checkout; do not present cached wrapper artifacts as the native preview. Keep the signing stash parked. diff --git a/docs/native-delivery-report.md b/docs/native-delivery-report.md index 4649fb5..820e0e0 100644 --- a/docs/native-delivery-report.md +++ b/docs/native-delivery-report.md @@ -49,7 +49,7 @@ Human security review and additional device/role/error-path coverage remain open 10 JVM tests, 6 native API-35 UI tests, the 7-test real-server suite (including those same 6), both Android lint variants and package builds pass. Swift has 16 -portable passes plus strict formatting/parsing. 23 script regressions, repository +portable passes plus strict formatting/parsing. 27 script regressions, repository checks, workflow lint and the 266-coordinate OSV scan pass. See [validation](validation.md) for evidence, failures corrected and exact limitations. @@ -62,10 +62,13 @@ state. Only the development-signed debug APK is installable as provided. ## 10. Gitea CI -The Android workflow offers `android-native-debug-apk`; the Mac workflow bootstraps -verified ARM64 Node before checkout. Actual artifact publication and Apple build -results require remote-run inspection. Remote Ubuntu emulator jobs need KVM access; -local emulator success is not a substitute for a passing remote matrix. +Android build job 1059 succeeded and uploaded `android-native-debug-apk`. The actual +artifact was downloaded, signature/version checked, installed and launched on the +authorized local emulator. [Download the verified artifact](https://git.nesterovic.cc/nessi/Yuvomi-App/actions/runs/660/artifacts/android-native-debug-apk). +Mac Node bootstrap and checkout now succeed, but its Apple-tool preflight stalled; +named/bounded probes have been added for diagnosis. The remote emulator matrix +still fails missing-KVM preflight, not native tests. No green whole-pipeline or +Apple application validation is claimed. See [validation](validation.md). ## 11. Signing and publication diff --git a/docs/validation.md b/docs/validation.md index 470bfc8..45bb570 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -21,7 +21,7 @@ not included in the native suites or counted toward native completion. | Android packages | Debug APK and unsigned release APK/AAB built successfully | | Swift portable suite | 16 passed; actual Foundation transport/contracts and URL/metadata policy, not Apple UI/Keychain execution | | Swift formatting/parsing | Strict format lint and parsing passed, including Apple-only sources | -| Script regressions | 10 runner-preflight, 8 JUnit-evidence and 5 screenshot-output tests passed | +| Script regressions | 14 runner-preflight/Apple-probe, 8 JUnit-evidence and 5 screenshot-output tests passed | | Repository/workflows | Source invariants, Python compilation, diff whitespace and actionlint for all 3 Gitea workflows passed | | OSV | No listed advisories for 266 locked/direct Maven coordinates and plugins; not a security audit | @@ -51,10 +51,31 @@ Evidence paths in the local external build directory: `/home/neste/yuvomi-native-integration-final.log`, `/home/neste/yuvomi-native-swift.log`. +## Observed Gitea results for source `40a7962` + +- Android run #5 (global run 660), build job 1059: **success**, including packages, + JVM/lint, formatting/repository/script checks, OSV and both artifact uploads. +- The actual `android-native-debug-apk` ZIP was downloaded. It contains exactly + `app-debug.apk`, version `0.2.0-native-preview`/code 2; APK v2 signature verified, + no fixture certificate/private-key files, installed and launched on the local + authorized API-35 emulator. SHA-256 of the downloaded APK: + `48d58ba5663b2e4fe563064efc567732e8ea35588cb610660f60406e82ccf610`. + Its ephemeral CI debug certificate differs from the local development key, so + its checksum intentionally differs from the locally collected APK. +- [Download the verified native debug artifact](https://git.nesterovic.cc/nessi/Yuvomi-App/actions/runs/660/artifacts/android-native-debug-apk) + (repository access required; normal 14-day artifact retention applies). +- Remote jobs 1060/1061 (API 35/37.0): **failed preflight** because `/dev/kvm` was + not exposed/writable. They executed no emulator tests. The matrix is not green. +- iOS run #6 (global 661), job 1062: verified native Node bootstrap and checkout + **succeeded**. Xcode 27.1/build 27A9275 printed, but the subsequent Apple-tool + preflight did not return during a 20-minute observation. No SwiftUI compile or + simulator/XCTest/Keychain/device success is claimed. Bounded, named tool probes + now fail with actionable diagnostics instead of silently hanging for the job's + full hour; their actual next Mac result still needs inspection. + No Apple SDK application build, simulator UI test or physical-device result is -claimed from these Linux runs. The modified Mac bootstrap and remote APK upload -still require inspection of the next actual Gitea run. Prior remote emulator jobs -failed KVM preflight; the prerequisite was not weakened or counted as passing. +claimed from the Linux runs or the stalled Mac job. Runner prerequisites were +not weakened or counted as passing, and no host license/settings were changed. ## Commands diff --git a/scripts/check-apple-tools.py b/scripts/check-apple-tools.py new file mode 100644 index 0000000..b681ca8 --- /dev/null +++ b/scripts/check-apple-tools.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Bounded Apple toolchain probes; never install components or change the host.""" +import subprocess +import sys + + +def probe(command, timeout=60): + label = " ".join(command) + print(f"Runner prerequisite: checking {label} (timeout {timeout}s)", flush=True) + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False) + except subprocess.TimeoutExpired as error: + raise RuntimeError( + f"{label} did not return within {timeout}s. Check selected Xcode, first-launch components " + "and CoreSimulator in the runner user's interactive login session; run this command there. " + "CI has not accepted licenses or changed host settings." + ) from error + except OSError as error: + raise RuntimeError(f"Cannot start {label}: {error}") from error + if result.returncode: + raise RuntimeError(f"{label} exited {result.returncode}: {(result.stderr or result.stdout).strip()}") + return result.stdout + + +def main(): + try: + print(probe(["xcodebuild", "-version"]).strip(), flush=True) + probe(["xcrun", "--find", "swift"]) + probe(["xcrun", "simctl", "list", "devices", "available"]) + except RuntimeError as error: + print(f"Runner prerequisite: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/check-ci-runner.sh b/scripts/check-ci-runner.sh index e2ef212..6cad3ff 100644 --- a/scripts/check-ci-runner.sh +++ b/scripts/check-ci-runner.sh @@ -17,9 +17,7 @@ case "${1:-}" in ios) [[ "$(uname -s)" == Darwin && "$(uname -m)" == arm64 ]] || fail 'macos-arm64 must execute natively on the Apple Silicon host' command -v xcodebuild >/dev/null || fail 'install full Xcode and select it with xcode-select' - xcodebuild -version - xcrun --find swift >/dev/null - xcrun simctl list devices available >/dev/null + python3 "${BASH_SOURCE[0]%/*}/check-apple-tools.py" ;; *) fail 'usage: bash scripts/check-ci-runner.sh android|emulator|ios' ;; esac diff --git a/scripts/test-ci-runner.py b/scripts/test-ci-runner.py index c4cb49d..34ca34f 100644 --- a/scripts/test-ci-runner.py +++ b/scripts/test-ci-runner.py @@ -1,24 +1,32 @@ #!/usr/bin/env python3 """Test runner prerequisite decisions with fake tools, not actual Apple SDKs.""" import os +import importlib.util from pathlib import Path +import shlex import subprocess +import sys import tempfile import unittest SCRIPT = Path(__file__).with_name("check-ci-runner.sh") +SPEC = importlib.util.spec_from_file_location("apple_tools", SCRIPT.with_name("check-apple-tools.py")) +APPLE_TOOLS = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(APPLE_TOOLS) class RunnerPrerequisiteTests(unittest.TestCase): - def run_check(self, mode, system="Linux", architecture="x86_64", missing=None, broken_xcode=False): - with tempfile.TemporaryDirectory(prefix="yuvomi-runner-test-") as temporary: + def run_check(self, mode, system="Linux", architecture="x86_64", missing=None, broken_xcode=False, broken_xcrun=False): + base = "/tmp/opencode" if Path("/tmp/opencode").exists() else None + with tempfile.TemporaryDirectory(prefix="yuvomi-runner-test-", dir=base) as temporary: directory = Path(temporary) for name in ["git", "curl", "unzip", "python3", "xcodebuild", "xcrun"]: if name == missing: continue path = directory / name - status = 1 if name == "xcodebuild" and broken_xcode else 0 - path.write_text(f"#!/bin/sh\nexit {status}\n") + status = 1 if (name == "xcodebuild" and broken_xcode or name == "xcrun" and broken_xcrun) else 0 + path.write_text(f"#!/bin/sh\nexec {shlex.quote(sys.executable)} \"$@\"\n" if name == "python3" + else f"#!/bin/sh\nexit {status}\n") path.chmod(0o700) uname = directory / "uname" uname.write_text('#!/bin/sh\ncase "$1" in -s) echo "$TEST_SYSTEM";; -m) echo "$TEST_ARCH";; esac\n') @@ -62,6 +70,24 @@ class RunnerPrerequisiteTests(unittest.TestCase): def test_unknown_mode_fails(self): self.assertNotEqual(self.run_check("other").returncode, 0) + def test_missing_xcrun_is_actionable(self): + result = self.run_check("ios", system="Darwin", architecture="arm64", missing="xcrun") + self.assertNotEqual(result.returncode, 0) + self.assertIn("Cannot start xcrun", result.stderr) + + def test_broken_xcrun_is_not_a_pass(self): + result = self.run_check("ios", system="Darwin", architecture="arm64", broken_xcrun=True) + self.assertNotEqual(result.returncode, 0) + self.assertIn("xcrun --find swift exited 1", result.stderr) + + def test_unresponsive_apple_probe_times_out_with_guidance(self): + with self.assertRaisesRegex(RuntimeError, "interactive login session"): + APPLE_TOOLS.probe([sys.executable, "-c", "import time; time.sleep(30)"], timeout=0.05) + + def test_apple_probe_preserves_tool_failure_diagnostic(self): + with self.assertRaisesRegex(RuntimeError, "fixture tool failure"): + APPLE_TOOLS.probe([sys.executable, "-c", "import sys; sys.exit('fixture tool failure')"]) + if __name__ == "__main__": unittest.main()