Files
meeting-assistant/docs/macos-native-diagnostic.md
T

16 KiB
Raw Blame History

macOS 14 TCG prerequisite diagnostic

This manual candidate uses the existing Ubuntu/x64 Docker runner. Before downloading Apple Recovery it tests actual AVX/AVX2 instruction execution in the pinned QEMU binary, then probes a fresh macOS 14+ Recovery guest. It does not install macOS, erase a disk, provision .NET/CLT or run Meeting Assistant tests. Readiness is only a prerequisite for full native CI.

Profile and evidence

The existing Intel Celeron 1037U has neither AVX nor AVX2. KVM run 4187 at 720a431 reached macOS 13.6/x86_64/root and visible whole writable 64-GiB media. Diskutil timed out after 122 seconds; the sampler produced no report after 61 seconds. The screen remained at the Apple boot progress bar. CPU throttling, memory-limit/OOM events and container swap were zero; memory peaked at 2.67 GB. Host paging occurred. No unsupported-instruction crash or particular IPC wait is proved.

CryptexFixup 1.0.5 selects the installed/updated Rosetta Cryptex and patches APFS hash checking; it does not replace the running Recovery cache or emulate instructions. macOS 13 is outside the .NET 10 supported-OS policy. This candidate therefore uses macOS 14 and software CPU emulation without Cryptex. It changes the compatibility profile, not one isolated causal variable; actual success must be measured.

Earlier TCG run 4159 observed guest AVX2 with the upstream-selected Skylake model. Runs 4161/4163 measured slow native startup and reached the 40-minute host limit before readiness. They predated the UDIF CRC repair at 94a70b2, reuse of successful sw_vers output and capturing the large Recovery hash only once. They do not qualify this candidate. Host/workflow limits for the read-only mode are 90/95 minutes; a readiness pass does not establish that full installation/build/tests fit the pipeline.

Run 4188 with Haswell recorded a boot loop; first-reset run 4189 retained repeated supervisor instruction-fetch pagefaults at RIP/CR2 0x24b0 before native readiness. Run 4190 at 3aaab45 restored Skylake-Client-v4 and the upstream TCG -spec-ctrl mask. The actual AVX/AVX2 ROM passed (exit 33; AVX2-disabled control exit 0), and macOS 14's Darwin 23.6.0 kernel identified the Skylake CPU. It retained one kernel handoff, a running VM and later userspace execution without the earlier reset, but reached the 20-minute diagnostic deadline without the readiness hook. These observations do not identify Haswell as the original cause or qualify native tests.

Run 4190 used synchronous kernel serial output and QEMU interrupt/register tracing to preserve the failure context. Its kernel explicitly warned that synchronous output impacts performance. The current full candidate uses normal upstream boot arguments and only the existing iothread QEMU argument; it retains actual CPU/staging receipts independently of Docker log rotation. Its existing fresh-readiness gate precedes every installation permit. This allows one bounded full qualification to test boot performance and, only after readiness, installation/build/tests without duplicating the guest startup. No remote full result has qualified this candidate yet.

Full run 4191 at 9735db1 reached native Recovery: x86_64/root, Darwin 23.6.0 and successful launchd service queries. Both sw_vers attempts were stopped by the existing 45-second watchdog at about 50 seconds. Other successful commands took 24–47 seconds, and small log-copy batches took 85–181 seconds. Thus this run proves broad native startup latency and a probe-imposed abort, without proving a permanent sw_vers hang. Disk enumeration, installation and application tests were not reached. The next candidate obtains the version from the current guest's SystemVersion plist to reduce process launches; it does not claim that sw_vers has become functional.

Run 4192 at 6122be2 captured the actual 603-byte guest file and strictly parsed version 14.6.1. Its host reply was published successfully, but the guest's reply-existence check timed out before services or disk enumeration. Guest request/timeout UTC timestamps were not retained, so late delivery and 9p visibility cannot be distinguished. The current candidate removes this version reply: the guest publishes its raw file and a provisional version candidate; the host's full XML validation and exact result binding remain mandatory before any installation permit. The later installation-permit transport is still unqualified.

Run 4193 at 9164fe4 successfully derived the same current-file version in both guest and host. Its single diskutil list physical process was stopped at 124 seconds by the 120-second watchdog without output. The observer saw one runnable row and 3.18 seconds of accumulated CPU time, without a stack or proven IPC endpoint. ioreg was also stopped before producing output. The post-disk service gates, permit, installation and tests were not reached. A prior modified Recovery14 reference image has byte-identical SystemVersion contents but a different image hash; it contains StorageKit with the com.apple.storagekitd and com.apple.storagekitd.dm MachServices, not diskmanagementd. The next observation targets that actual service family and the own diskutil stack; this reference does not establish the live service state of run 4193.

Run 4194 at 81a3b9c stopped the same single query after 606 seconds without output. StorageKit exists but was not running and had never started in the before/live snapshots; the live snapshot covers approximately 103–160 seconds of the query, not its entire lifetime. sample hit its own watchdog without even its sampling-start message or report. This does not establish symbolication as the cause. The observed 1T is a current Timeshare priority, not a thread count or proof of background policy. Neither installation nor tests ran.

Entry points and dependencies

Orchestration remains the .NET 10 file-based app tools/ci/MacOsNativeDiagnostic.cs. Existing Bash/Python boot integration is necessary before a guest SDK exists. NASM assembles the CPU probe in the disposable image build, without host/runner installation. No new runner, device, capability, secret or service is used.

The disposable native process diagnostic uses a small C boundary linked only to libSystem, so it can record entry and kernel observations before the guest has .NET or CLT. Its C# file-based build driver and retained source/compiler/SDK/minimum-OS/import/signature/hash receipt describe the one-time local build. The pipeline receives this diagnostic asset and does not invoke an Apple compiler or request a macOS runner. This asset is not one of the application's four freshly built helpers and cannot qualify a test or readiness gate.

The probe validates both the target PID and expected parent before reading role/task data. Public BSD observations include background flags, Nice, role and raw CPU/page-in counters. A read-only Mach port is attempted separately, with return and errno recorded before any DYLD/thread reads. The local Apple-sleep fixture returned EPERM; actual Recovery rights remain unknown. The probe has no control-port fallback, process suspension, remote writes, extra entitlements or SIP change. Unsuspended snapshots may be incomplete.

For local maintenance with an existing Apple SDK, run dotnet run --file tools/ci/native-process-probe/ProbeDriver.cs -- tools/ci/native-process-probe. It builds and signs into that folder's artifacts/, observes and cleans up its own temporary sleep child, and retains the build/self-test receipt there. Publishing a changed asset requires reviewing that receipt, refreshing the portable build-manifest.json and updating all four controller hash pins. CI verifies those pins before staging the binary and manifest into the owned Recovery state; it never runs the build driver.

dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate --source /path/to/clean/pinned/dockur-clone --output /path/to/fresh/validation
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --output artifacts/native-macos
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos

The native diagnostic workflow is manual only. Temporary diagnostic branches are excluded from ordinary push jobs to avoid repeating unchanged Wine/portable jobs. Remove this routing when integrating qualified CI into the actual PR.

Before Apple downloads

The existing daemon must be Linux/x64 with two CPUs and 6 GiB memory; the runner must have 5 GiB available memory and the Docker filesystem 8 GiB free. These checks do not reconfigure resources. Dockur commit 16a5b470cdd601bae8b05b02d748d7edfb36c12e, both imported QEMU image digests and original source seams remain pinned.

Actual Skylake-Client-v4 CPU flags under TCG use enforce=on to reject unsupported requests. The CPU preflight uses that same composed flag list and QEMU binary before Recovery download/boot. tools/ci/macos-tcg-cpu-preflight.asm enables long mode/YMM state, executes AVX and AVX2 integer arithmetic, and checks an Int32 from the upper 128-bit lane. Only the correct result reaches QEMU debug-exit code 33. No disks/network attach; failure/timeout fails preflight. This tests that instruction chain, not the complete ISA or macOS.

The locally assembled NASM 2.16.03 ROM is 65,536 bytes, SHA256 c32746122cc68f3ed642aa46c21b677f803c58f0d4ff665841723fcc5625f549. Assembly/static review does not prove remote execution.

Native gates, bounds and cleanup

The original Apple recoveryosd runs under its existing job/PID beside the read-only probe. Exact known macOS 13/14 plist layouts and same-length replacements retain their allowlist. The patcher validates UDIF boundaries, updates changed mish/koly CRCs and reads back the image. Four raw/zlib positive and twelve rejection fixtures use an independent C# CRC32 reader. Apple chunklist authentication applies to the input, not the deliberately modified image.

Native readiness requires x86_64, UID 0, macOS 14+, successful launchd service queries and exactly one writable whole 64-GiB disk. The guest's existing Bash runtime reads its own /System/Library/CoreServices/SystemVersion.plist with a 4-KiB bound and mandatory EOF. Apple documents this path as the system-version source. Bash extracts only a provisional numeric version from the same bytes it publishes; subsequent guest probes remain read-only. The existing C# controller parses the full captured XML with external resolution disabled and requires a flat string-valued dictionary with exactly one valid direct ProductVersion string and macOS 14+. Missing, binary, oversized, ambiguous or malformed content fails. Before either readiness success or an installation permit, the result's version must exactly match this current-file evidence. No configured VERSION or host OS value serves as proof. The actual native diskutil query remains mandatory.

The raw file, exact source path, length, SHA256 and parsing receipt are retained and bound to the current token. This version evidence travels only from guest to host and requires no reply. The guest uses its existing Bash before any SDK exists; authoritative XML logic stays in C#/.NET. A Bash candidate alone cannot authorize installation or qualify readiness. This method establishes the current guest version, not successful execution of sw_vers.

Required commands retain 45 seconds and UID 180 seconds. The single disk query retains its 600-second diagnostic window under the existing 90-minute Recovery deadline. An optional no-target sample CLI control precedes it. The owned observer takes an early native process snapshot, requests an ordinary one-second/100-ms sample without eager -mayDie symbol loading, and records live StorageKit state. After a cancelable 180-second builtin pause it takes a late snapshot of the same live disk child and expected parent. Each observation retains its own 60-second watchdog and two-second TERM/KILL grace. Missing tools, denied reads, failures and timed-out samples remain explicit missing evidence. Stack output is captured directly from the owned state with a 512-KiB bound, without another native copy command. Observation failure passes no gate. Owned children are stopped on query completion/cancellation; output remains 512 KiB per command and 4 MiB proof. No service is started or restarted by the observer.

The container retains 6 GiB memory/swap, two-CPU limit, 512 MiB shared memory and a 4-GiB/two-vCPU guest. One fresh anonymous /storage volume holds the sparse 64-GiB target. Inspection rejects devices, capabilities, binds, ports, host networking and privileged mode. KVM is disabled with no /dev/kvm mapping; guest networking stays slirp.

Evidence retains run/source/profile identity, CPU preflight, original/patched Recovery identity, container/QEMU state, native proof/result and cleanup. Sparse kernel-handoff lines are retained separately; two handoffs before readiness/installation permission fail early. After permission, normal installer reboots remain allowed. Optional bounded before/during/after pressure snapshots record host/cgroup counters. The /storage/14/setup.dmg hash is captured once after staging; successful evidence survives later capture failure. Screenshots/pressure observations pass no gate.

Both cleanup paths verify exact token/label/ID before removing only the owned container, anonymous volume and image. No pruning, host changes, original checkout changes or Meeting Assistant restart occurs. Artifacts remain seven days. Full CI remains unverified until an installed supported guest builds/signs fresh helpers and passes all 577 tests, including the five native macOS tests, with zero skips.

Prepared full build/test flow

The separate manual .gitea/workflows/macos-native-full.yaml and the prepared required PR job invoke the same full mode:

dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --validate --full --source /path/to/clean/pinned/dockur-clone --output /path/to/fresh/full-validation
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --run --full --output artifacts/native-macos-full
dotnet run --file tools/ci/MacOsNativeDiagnostic.cs -- --cleanup --output artifacts/native-macos-full

Full mode repeats CPU preflight and Recovery readiness for its own fresh guest. Before erasing the disposable target, the host verifies the owned container/anonymous volume, raw 64-GiB image, actual QEMU attachment/unique disk serial and state share. A token/source/disk-bound permit authorizes the guest. The guest independently checks whole/writable/size/unique serial before diskutil eraseDisk. It never erases a host disk or reuses an unrelated guest volume.

The installer provisions the owned guest and returns into the prepared firstboot hook. Early firstboot logs and failures enter the state share even before account-package installation or test bootstrap. The installed root must be APFS backed by the exact owned physical store. Apple softwareupdate provisions CLT; a real Swift/SDK smoke build imports the required Apple frameworks. The source archive is bound to clean Git HEAD and SHA256; the pinned macOS x64 .NET SDK 10.0.401 is checked with SHA512. No prebuilt application/helper result counts as this run's evidence.

tools/ci/MacOsNativeGuest.cs restores/builds/tests the source inside the installed guest. Success requires four fresh x86_64 Mach-O helpers, a valid audio-app signature and a fresh source-bound TRX containing exactly 577 distinct passing tests, zero failures/skips and all five named native macOS tests. Archive, SDK, build, signature and TRX receipts are retained. The required PR job follows the existing Wine and portable jobs; all jobs still select ubuntu-latest.

The workflow has 180 minutes; the controller reserves cleanup time with a shared 172-minute total deadline. Recovery, installation, CLT and test caps are 90/80/30/25 minutes under that same total, not additive promises. Actual supported-guest installation/performance and remote test success remain unqualified. The full run's own mandatory fresh-readiness and owned-disk gates prevent installation until that guest passes its prerequisites. CI does not deploy or restart the workstation application.