prepare NoAVX recovery dispatch with memory admission and progress logs

This commit is contained in:
dh committed 2026-10-05 13:20:02 +02:00
1 parent 7ea1c7f517
commit fef676c810
3 files changed
+59 -5

No files matched your search

@@ -7,6 +7,7 @@ on:
# Its manual workflow provides that evidence; the PR branch still runs all jobs. # Its manual workflow provides that evidence; the PR branch still runs all jobs.
branches-ignore: branches-ignore:
- codex/macos-ci-kvm-compatibility - codex/macos-ci-kvm-compatibility
- codex/macos-kvm-noavx-recovery
workflow_dispatch: workflow_dispatch:
jobs: jobs:
+4
View File
@@ -6,6 +6,10 @@ Baseline: bootstrap commit `4606de069678e8f95dfe3c7dad1bf5ce5384d30c`; separate
The NoAVX continuation compares against KVM Recovery commit `720a431`. Its only guest change is adding `NoAVXFSCompressionTypeZlib-AVXpel.kext` to the existing OpenCore overlay and `Kernel.Add`. Existing Lilu/CryptexFixup, CPU passthrough, macOS 13 Recovery, disk, probes and deadlines are preserved. The hypothesis is that an AVX-dependent filesystem decompression path blocks native file loading on the Celeron; this has not been established as the cause of the disk-readiness hang. Application source is unchanged, and this candidate has only offline validation evidence. The NoAVX continuation compares against KVM Recovery commit `720a431`. Its only guest change is adding `NoAVXFSCompressionTypeZlib-AVXpel.kext` to the existing OpenCore overlay and `Kernel.Add`. Existing Lilu/CryptexFixup, CPU passthrough, macOS 13 Recovery, disk, probes and deadlines are preserved. The hypothesis is that an AVX-dependent filesystem decompression path blocks native file loading on the Celeron; this has not been established as the cause of the disk-readiness hang. Application source is unchanged, and this candidate has only offline validation evidence.
The host-memory admission check is copied unchanged from RAW candidate `ec5508e`: the unchanged 4-GiB guest plus 512 MiB QEMU overhead requires 4.5 GiB available. Offline validation accepts the captured run-4204 value of 5,138,696 KiB and rejects 4 GiB, missing and invalid values. Guest RAM and the 6-GiB container cap are unchanged. This admission budget reserves no host memory against other workloads. The `codex/macos-kvm-noavx-recovery` branch skips only the PR/Push workflow's push trigger; pull requests and manual workflows retain their existing triggers.
The existing one-minute heartbeat prints at most the last two captured `[proof-start]`, `[proof-done]`, `[proof-native-wait]`, `[proof-result]` or `[native-version]` lines, each capped at 256 characters. `[proof-native-wait]` is this candidate's existing command-completion marker. It reads only the already retained `guest-proof.log`; no additional Docker/guest query or polling timer is added. Five offline fixture cases verify marker selection, missing/unrelated output and the bounds. The controlled 40-minute host deadline and existing cleanup are preserved.
## Reasons and remaining gaps ## Reasons and remaining gaps
The existing daemon's Intel Celeron 1037U lacks AVX/AVX2; a separate diagnostic proved KVM enabled/paused state and clean exit. `CPU_MODEL=host` preserves actual instruction availability rather than advertising AVX2 through emulated Skylake. This candidate refuses a TCG or CPU-model fallback. The existing daemon's Intel Celeron 1037U lacks AVX/AVX2; a separate diagnostic proved KVM enabled/paused state and clean exit. `CPU_MODEL=host` preserves actual instruction availability rather than advertising AVX2 through emulated Skylake. This candidate refuses a TCG or CPU-model fallback.
+54 -5
View File
@@ -65,6 +65,8 @@ static class NativeDiagnostic
var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos"); var output = Path.GetFullPath(Option(args, "--output") ?? "artifacts/native-macos");
if (args.Contains("--validate")) if (args.Contains("--validate"))
{ {
ValidateRunnerMemoryGate();
ValidateGuestProgress(output);
ValidateContracts(); ValidateContracts();
if (Option(args, "--source") is { } source) if (Option(args, "--source") is { } source)
{ {
@@ -113,9 +115,8 @@ static class NativeDiagnostic
throw new InvalidOperationException("Existing Docker resources cannot fit this bounded 2-CPU/6-GiB diagnostic; no infrastructure change was requested."); throw new InvalidOperationException("Existing Docker resources cannot fit this bounded 2-CPU/6-GiB diagnostic; no infrastructure change was requested.");
} }
await Command("sh", ["-c", "cat /proc/meminfo; printf '\n[cgroup]\n'; cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max 2>/dev/null || true; printf '\n[workspace disk]\n'; df -Pk ."], output, "runner-resources", deadline.Token); await Command("sh", ["-c", "cat /proc/meminfo; printf '\n[cgroup]\n'; cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max 2>/dev/null || true; printf '\n[workspace disk]\n'; df -Pk ."], output, "runner-resources", deadline.Token);
var available = System.Text.RegularExpressions.Regex.Match(File.ReadAllText("/proc/meminfo"), @"(?m)^MemAvailable:\s+(\d+) kB$"); if (!HasAvailableGuestMemory(File.ReadAllText("/proc/meminfo")))
if (!available.Success || long.Parse(available.Groups[1].Value) < 5L * 1024 * 1024) throw new InvalidOperationException("Existing runner memory cannot fit the 4-GiB guest plus its 512-MiB QEMU overhead budget; no infrastructure change was requested.");
throw new InvalidOperationException("Existing runner memory has less than the 5-GiB available diagnostic budget; no infrastructure change was requested.");
var source = Path.Combine(work, "dockur"); var source = Path.Combine(work, "dockur");
await Command("git", ["clone", "--no-checkout", "https://github.com/dockur/macos.git", source], output, "dockur-clone", deadline.Token); await Command("git", ["clone", "--no-checkout", "https://github.com/dockur/macos.git", source], output, "dockur-clone", deadline.Token);
await Command("git", ["-C", source, "checkout", "--detach", DockurCommit], output, "dockur-checkout", deadline.Token); await Command("git", ["-C", source, "checkout", "--detach", DockurCommit], output, "dockur-checkout", deadline.Token);
@@ -136,7 +137,7 @@ static class NativeDiagnostic
AssertContainer(File.ReadAllText(Path.Combine(output, "container-created.stdout.log")), token); AssertContainer(File.ReadAllText(Path.Combine(output, "container-created.stdout.log")), token);
await Command("docker", ["start", id], output, "docker-start", deadline.Token); await Command("docker", ["start", id], output, "docker-start", deadline.Token);
await CapturePressure(id, output, "before", deadline.Token); await CapturePressure(id, output, "before", deadline.Token);
Console.WriteLine("The owned restricted KVM/host-CPU macOS 13 compatibility guest is starting. Success requires native macOS 13+/x86_64 and a writable 64-GiB disk; no installer will run. This is not a single-variable causal test."); Console.WriteLine("The owned restricted KVM/host-CPU macOS 13 compatibility guest is starting. Success requires native macOS 13+/x86_64 and a writable 64-GiB disk; no installer will run. The additional NoAVX boot kext is the only guest variable against KVM baseline 720a431.");
var recoveryStarted = Stopwatch.StartNew(); var recoveryStarted = Stopwatch.StartNew();
var heartbeat = Stopwatch.StartNew(); var heartbeat = Stopwatch.StartNew();
var diskPressureCaptured = false; var diskPressureCaptured = false;
@@ -163,7 +164,8 @@ static class NativeDiagnostic
if (running.Output.Trim() != "true") throw new InvalidOperationException("Guest container exited before a native readiness result."); if (running.Output.Trim() != "true") throw new InvalidOperationException("Guest container exited before a native readiness result.");
if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60)) if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60))
{ {
Console.WriteLine($"[native-diagnostic] phase=recovery; elapsed={recoveryStarted.Elapsed.TotalMinutes:F1} minutes; container=running; readiness=pending"); Console.WriteLine($"[native-diagnostic] phase=recovery; elapsed={recoveryStarted.Elapsed.TotalMinutes:F1}/40 minutes; container=running; readiness=pending");
foreach (var progress in LastGuestProgress(proofPath)) Console.WriteLine("[native-diagnostic] guest-progress=" + progress);
heartbeat.Restart(); heartbeat.Restart();
} }
await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token); await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token);
@@ -430,6 +432,53 @@ static class NativeDiagnostic
return (boot, document.ToString(), assets); return (boot, document.ToString(), assets);
} }
static bool HasAvailableGuestMemory(string meminfo)
{
var available = System.Text.RegularExpressions.Regex.Match(meminfo, @"(?m)^MemAvailable:\s+(\d+) kB$");
return available.Success && long.TryParse(available.Groups[1].Value, out var kib) && kib >= 4L * 1024 * 1024 + 512L * 1024;
}
static string[] LastGuestProgress(string path)
{
if (!File.Exists(path)) return [];
var markers = new[] { "[proof-start]", "[proof-done]", "[proof-native-wait]", "[proof-result]", "[native-version]" };
return File.ReadLines(path).Where(line => markers.Any(marker => line.StartsWith(marker, StringComparison.Ordinal)))
.TakeLast(2).Select(line => line[..Math.Min(line.Length, 256)]).ToArray();
}
static void ValidateGuestProgress(string output)
{
var fixture = Path.Combine(output, "validation-guest-progress");
Directory.CreateDirectory(fixture);
var path = Path.Combine(fixture, "guest-proof.log");
File.Delete(path);
if (LastGuestProgress(path).Length != 0) throw new InvalidOperationException("Missing guest progress was fabricated.");
File.WriteAllText(path, "[proof-start] platform child=12\nignored native output\n[proof-native-wait] platform exit=0\n[proof-start] uid child=13\n", new UTF8Encoding(false));
if (!LastGuestProgress(path).SequenceEqual(new[] { "[proof-native-wait] platform exit=0", "[proof-start] uid child=13" })) throw new InvalidOperationException("Heartbeat must show the last two captured native progress markers.");
File.WriteAllText(path, "[proof-done] uid\n[native-version] 13.6\n[proof-result] true: readiness\n", new UTF8Encoding(false));
if (!LastGuestProgress(path).SequenceEqual(new[] { "[native-version] 13.6", "[proof-result] true: readiness" })) throw new InvalidOperationException("Heartbeat lost native version/result progress.");
File.WriteAllText(path, "[proof-start] " + new string('x', 1024) + "\n", new UTF8Encoding(false));
if (LastGuestProgress(path).Single().Length != 256) throw new InvalidOperationException("Heartbeat progress line exceeded its output bound.");
File.WriteAllText(path, "unrelated output\n", new UTF8Encoding(false));
if (LastGuestProgress(path).Length != 0) throw new InvalidOperationException("Heartbeat selected unrelated guest output.");
Save(Path.Combine(fixture, "receipt.json"), new { success = true, fixtureCases = 5, maximumLines = 2, maximumLineCharacters = 256, existingProofOnly = true, dockerExecuted = false, guestExecuted = false });
}
static void ValidateRunnerMemoryGate()
{
// Run 4204: 4-GiB guest plus 512-MiB QEMU overhead fits its captured available memory.
var cases = new[]
{
("captured-run4204", "MemTotal: 16281732 kB\nMemAvailable: 5138696 kB\n", true),
("below-guest-plus-overhead", "MemAvailable: 4194304 kB\n", false),
("missing", "MemTotal: 16281732 kB\n", false),
("invalid", "MemAvailable: unavailable kB\n", false)
};
foreach (var (name, meminfo, expected) in cases)
if (HasAvailableGuestMemory(meminfo) != expected)
throw new InvalidOperationException("Existing runner memory admission failed: " + name);
}
static Dictionary<string, byte[]> ReadNoAvxArchive(byte[] bytes) static Dictionary<string, byte[]> ReadNoAvxArchive(byte[] bytes)
{ {
if (bytes.Length != 98356 || Hash(bytes) != NoAvxHash) throw new InvalidOperationException("Pinned OCLP NoAVX archive size/hash mismatch."); if (bytes.Length != 98356 || Hash(bytes) != NoAvxHash) throw new InvalidOperationException("Pinned OCLP NoAVX archive size/hash mismatch.");