//! In-kernel test cases, run at the end of bring-up when the kernel is built with
//! `-Dtest-case=<name>`. Each case writes structured markers to the serial port
//! that the QEMU harness (test/qemu_test.py) asserts on:
//!
//!   [PASS]/[FAIL] <check>          per assertion
//!   DANOS-TEST-RESULT: PASS|FAIL   overall, for non-faulting cases
//!
//! Faulting cases (fault-ud, fault-pf, fault-df) deliberately don't return a
//! result line — they trigger a CPU exception, and the harness asserts on the
//! exception report the handler prints (which also reaches serial).

const std = @import("std");
const boot_handoff = @import("boot-handoff");
const abi = @import("abi");
const device_abi = @import("device-abi");
const architecture = @import("architecture");
const wall_clock = @import("wall-clock.zig");
const devices_broker = @import("devices-broker.zig");
const platform = @import("platform");
const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const scheduler = @import("scheduler.zig");
const ipc = @import("ipc.zig");
const ipcsync = @import("ipc-synchronous.zig");
const irq = @import("irq.zig");
const sync = @import("sync.zig");
const process = @import("process.zig");
const initial_ramdisk = @import("initial-ramdisk");
const kernel_log = @import("log.zig");
const kernel_vfs = @import("vfs.zig");

/// Formatted test-marker write. Goes through the kernel log (not straight to
/// serial): the log lock is what keeps marker lines from interleaving with
/// concurrent user-process records on other cores.
fn log(comptime fmt: []const u8, args: anytype) void {
    kernel_log.print(fmt, args);
}

var passed: u32 = 0;
var failed: u32 = 0;

fn check(name: []const u8, ok: bool) void {
    if (ok) {
        passed += 1;
        log("[PASS] {s}\n", .{name});
    } else {
        failed += 1;
        log("[FAIL] {s}\n", .{name});
    }
}

/// Emit the overall result line the harness matches, then the done sentinel.
fn result() void {
    log("DANOS-TEST-RESULT: {s} ({d} passed, {d} failed)\n", .{
        if (failed == 0) "PASS" else "FAIL",
        passed,
        failed,
    });
    log("DANOS-TEST-DONE\n", .{});
}

pub fn run(case: []const u8, boot_information: *const BootInformation) void {
    if (eql(case, "smoke")) {
        smoke(boot_information);
    } else if (eql(case, "discovery")) {
        discoveryTest();
    } else if (eql(case, "wx")) {
        wxTest();
    } else if (eql(case, "timer")) {
        timer();
    } else if (eql(case, "clock")) {
        clock();
    } else if (eql(case, "wall-clock")) {
        wallClock();
    } else if (eql(case, "vmm")) {
        vmm();
    } else if (eql(case, "heap")) {
        heapTest();
    } else if (eql(case, "sched")) {
        schedulerTest();
    } else if (eql(case, "priority")) {
        priorityTest();
    } else if (eql(case, "sleep")) {
        sleepTest();
    } else if (eql(case, "event")) {
        eventTest();
    } else if (eql(case, "ipc")) {
        ipcTest();
    } else if (eql(case, "ipc-call")) {
        ipcCallTest();
    } else if (eql(case, "ipc-cap")) {
        capabilityTest();
    } else if (eql(case, "dma")) {
        dmaTest();
    } else if (eql(case, "msi")) {
        msiTest();
    } else if (eql(case, "iommu")) {
        iommuTest();
    } else if (eql(case, "ioport")) {
        ioPortTest();
    } else if (eql(case, "display")) {
        displayTest(boot_information);
    } else if (eql(case, "display-service")) {
        displayServiceTest(boot_information);
    } else if (eql(case, "display-demo")) {
        displayDemoTest(boot_information);
    } else if (eql(case, "display-cursor")) {
        displayCursorTest(boot_information);
    } else if (eql(case, "shared-memory")) {
        sharedMemoryTest(boot_information);
    } else if (eql(case, "virtio-gpu")) {
        virtioGpuTest(boot_information);
    } else if (eql(case, "display-native")) {
        displayNativeTest(boot_information);
    } else if (eql(case, "display-reattach")) {
        displayReattachTest(boot_information);
    } else if (eql(case, "clock")) {
        clockTest();
    } else if (eql(case, "smp")) {
        smpTest();
    } else if (eql(case, "affinity")) {
        affinityTest();
    } else if (eql(case, "smp-stress")) {
        stressTest();
    } else if (eql(case, "smp-retry")) {
        smpRetryTest();
    } else if (eql(case, "tsc-sync")) {
        tscSyncTest();
    } else if (eql(case, "fault-ud")) {
        faultInvalidOpcode();
    } else if (eql(case, "fault-pf")) {
        faultPageFault();
    } else if (eql(case, "fault-df")) {
        faultDoubleFault();
    } else if (eql(case, "fault-ap-df")) {
        faultApTest();
    } else if (eql(case, "fault-nx")) {
        faultNoExecute();
    } else if (eql(case, "fault-null")) {
        faultNull();
    } else if (eql(case, "usermem")) {
        userMemTest();
    } else if (eql(case, "user-pf")) {
        userPfTest();
    } else if (eql(case, "fault-recovery")) {
        faultRecoveryTest(boot_information);
    } else if (eql(case, "address-space-refcount")) {
        addressSpaceRefcountTest(boot_information);
    } else if (eql(case, "thread-spawn")) {
        threadSpawnTest(boot_information);
    } else if (eql(case, "thread-join")) {
        threadJoinTest(boot_information);
    } else if (eql(case, "thread-futex")) {
        threadFutexTest(boot_information);
    } else if (eql(case, "thread-mutex")) {
        threadMutexTest(boot_information);
    } else if (eql(case, "thread-id")) {
        threadIdTest(boot_information);
    } else if (eql(case, "thread-alloc")) {
        threadAllocTest(boot_information);
    } else if (eql(case, "task-reap")) {
        taskReapTest(boot_information);
    } else if (eql(case, "thread-tls")) {
        threadTlsTest(boot_information);
    } else if (eql(case, "thread-rwlock")) {
        threadRwlockTest(boot_information);
    } else if (eql(case, "args")) {
        argsTest(boot_information);
    } else if (eql(case, "init")) {
        initTest(boot_information);
    } else if (eql(case, "process")) {
        processTest(boot_information);
    } else if (eql(case, "process-list")) {
        processListTest(boot_information);
    } else if (eql(case, "process-kill")) {
        processKillTest(boot_information);
    } else if (eql(case, "supervision")) {
        supervisionTest(boot_information);
    } else if (eql(case, "claim-release")) {
        claimReleaseTest(boot_information);
    } else if (eql(case, "vfs-client-death")) {
        vfsClientDeathTest(boot_information);
    } else if (eql(case, "signals")) {
        signalsTest(boot_information);
    } else if (eql(case, "driver-restart")) {
        driverRestartTest(boot_information);
    } else if (eql(case, "usb-report")) {
        usbReportTest(boot_information);
    } else if (eql(case, "usb-hid")) {
        usbHidTest(boot_information);
    } else if (eql(case, "usb-storage")) {
        usbStorageTest(boot_information);
    } else if (eql(case, "fat-mount")) {
        fatMountTest(boot_information);
    } else if (eql(case, "device-list")) {
        deviceListTest(boot_information);
    } else if (eql(case, "pci-scan")) {
        pciScanTest(boot_information);
    } else if (eql(case, "acpi-parse")) {
        acpiParseTest(boot_information);
    } else if (eql(case, "acpi-report")) {
        acpiReportTest(boot_information);
    } else if (eql(case, "acpi-ps2")) {
        acpiReportTest(boot_information); // same spawn; the harness regex differs
    } else if (eql(case, "power-button")) {
        acpiReportTest(boot_information); // boot the manager (spawns the acpi service); harness injects the button
    } else if (eql(case, "orderly-shutdown")) {
        orderlyShutdownTest(boot_information);
    } else if (eql(case, "initial-ramdisk")) {
        initialRamdiskTest(boot_information);
    } else if (eql(case, "vfs")) {
        vfsTest(boot_information);
    } else if (eql(case, "kvfs")) {
        kernelVfsTest(boot_information);
    } else if (eql(case, "input")) {
        inputTest(boot_information);
    } else if (eql(case, "iopass")) {
        ioPassTest();
    } else if (eql(case, "irqfree")) {
        irqFreeTest();
    } else if (eql(case, "containment")) {
        containmentTest();
    } else if (eql(case, "device-manager")) {
        deviceManagerTest(boot_information);
    } else if (eql(case, "reboot")) {
        rebootTest();
    } else {
        log("DANOS-TEST-RESULT: FAIL (unknown case '{s}')\n", .{case});
    }
}

fn platformHal() platform.Hal {
    return .{
        .mapMmio = architecture.mapMmio,
        .pioRead = architecture.pioRead,
        .pioWrite = architecture.pioWrite,
    };
}

/// Drive an ACPI power transition. On success the machine powers off or resets,
/// so QEMU exits — the harness observes the process exit. If control returns, the
/// transition failed and we emit a FAIL result.
// Soft-off (S5) is no longer a kernel operation — the ring-3 acpi service owns it
// (exercised end-to-end by `orderly-shutdown`). Reboot stays in the kernel (FADT
// reset register, no AML), so it keeps its own case.
fn rebootTest() void {
    log("DANOS-TEST-BEGIN: reboot\n", .{});
    const hal = platformHal();
    log("DANOS-POWER: attempting reboot\n", .{});
    platform.reboot(hal);
    check("power transition took effect", false);
    result();
}

const BootInformation = boot_handoff.BootInformation;

fn eql(a: []const u8, b: []const u8) bool {
    return std.mem.eql(u8, a, b);
}

/// Whether the captured last-write buffer *contains* `needle`. Markers are
/// matched as substrings, not prefixes, so a service's source-path debug prefix
/// (`system/drivers/pci-bus: ...`) still satisfies a marker like `pci-bus: `.
fn bufferHas(needle: []const u8) bool {
    return std.mem.indexOf(u8, process.write_buffer[0..process.write_len], needle) != null;
}

/// Substring search across the whole retained log ring (record payloads are
/// contiguous in the stream, so a one-line needle always matches if present).
/// Unlike `bufferHas` (the single LAST write), this survives busy-tree chatter.
fn ringHas(needle: []const u8) bool {
    var chunk: [1024]u8 = undefined;
    var overlap: [128]u8 = undefined;
    var overlap_len: usize = 0;
    var offset = kernel_log.status().tail;
    while (true) {
        const n = kernel_log.readAt(offset, &chunk) orelse return false;
        if (n == 0) return false;
        offset += n;
        // Search the previous tail glued to this chunk, then the chunk itself.
        if (overlap_len != 0) {
            var glued: [1152]u8 = undefined;
            @memcpy(glued[0..overlap_len], overlap[0..overlap_len]);
            const m = @min(n, glued.len - overlap_len);
            @memcpy(glued[overlap_len..][0..m], chunk[0..m]);
            if (std.mem.indexOf(u8, glued[0 .. overlap_len + m], needle) != null) return true;
        } else if (std.mem.indexOf(u8, chunk[0..n], needle) != null) return true;
        overlap_len = @min(n, @min(overlap.len, needle.len));
        @memcpy(overlap[0..overlap_len], chunk[n - overlap_len ..][0..overlap_len]);
    }
}

/// How many `pci_device` functions the devices broker currently holds. A durable
/// snapshot, unlike a `bufferHas` poll of the single-latest write_buffer line, so a
/// test can wait on it without racing transient log output. `scratch` is
/// caller-owned to keep the (large) descriptor array off this helper's own frame.
fn brokerPciCount(scratch: []device_abi.DeviceDescriptor) u32 {
    const k = @min(devices_broker.enumerate(scratch), scratch.len);
    var count: u32 = 0;
    for (scratch[0..k]) |d| {
        if (d.class == @intFromEnum(device_abi.DeviceClass.pci_device)) count += 1;
    }
    return count;
}

/// Non-destructive checks of the memory map and frame allocator.
fn smoke(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: smoke\n", .{});

    // The memory map has some usable RAM.
    const mm = boot_information.memory_map;
    const regions = @as([*]const boot_handoff.MemoryRegion, @ptrFromInt(boot_handoff.physicalToVirtual(mm.regions)))[0..mm.len];
    var usable: u64 = 0;
    for (regions) |r| {
        if (r.kind == .usable) usable += r.pages;
    }
    check("memory map reports usable RAM", usable > 0);

    // The frame allocator hands out distinct, page-aligned frames.
    const a = pmm.alloc();
    const b = pmm.alloc();
    check("alloc returns a frame", a != null);
    check("alloc returns distinct frames", a != null and b != null and a.? != b.?);
    check("frames are page-aligned", (a orelse 1) % abi.page_size == 0);

    // Freeing restores the count.
    const before = pmm.stats().free_frames;
    if (a) |p| pmm.free(p);
    if (b) |p| pmm.free(p);
    check("free returns frames to the pool", pmm.stats().free_frames == before + 2);

    // Paging is active on our own tables (the root is non-zero and page-aligned).
    const root = architecture.activePageTable();
    check("paging active (page-table root set)", root != 0 and root % abi.page_size == 0);

    result();
}

/// Verify device interrupts fire and return: the timer tick counter must advance
/// on its own. Interrupts are already enabled by kmain before tests run.
fn timer() void {
    log("DANOS-TEST-BEGIN: timer\n", .{});
    const start = architecture.ticks();
    // Busy-wait for the counter to advance. architecture.ticks() is a volatile load, so
    // the compiler re-reads it each iteration and sees the interrupt's update.
    // The cap is only a safety net; the harness timeout is the real backstop.
    var spins: u64 = 0;
    while (architecture.ticks() == start and spins < 5_000_000_000) spins +%= 1;
    check("timer interrupts advance the tick count", architecture.ticks() > start);

    result();
}

/// Verify device discovery populated the platform facts the rest of the kernel
/// depends on — the results the static ACPI tables stashed in globals at boot.
/// These are stable for the QEMU q35 + OVMF machine the harness runs, and span the
/// tables: MADT (LAPIC base, CPU count) and FADT (PM/reset registers). The kernel
/// no longer interprets AML — sleep types are the ring-3 acpi service's concern.
fn discoveryTest() void {
    log("DANOS-TEST-BEGIN: discovery\n", .{});
    const pinfo = platform.platformInformation();
    const pw = platform.powerInformation();

    check("LAPIC base discovered (MADT)", pinfo.lapic_base == 0xFEE00000);
    check("ACPI PM timer found (FADT)", pinfo.pm_timer.present());
    check("PM1a control register found (FADT)", pw.pm1a_cnt.present());
    check("reset register supported (FADT)", pw.reset_supported);
    check("at least one CPU enumerated (MADT)", platform.cpus().len >= 1);

    // M15: every PCI function now carries its own 4 KiB ECAM configuration space as
    // resource 0 — the window a driver mmio_maps to walk its capability list (MSI etc).
    // M19.3: the kernel seeds only the bridge; functions arrive by the ring-3
    // scan (proven equivalent in pci-scan before the walk retired).
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    const n = @min(devices_broker.enumerate(&buffer), buffer.len);
    var bridges: u32 = 0;
    var bridge_shape_ok = false;
    for (buffer[0..n]) |d| {
        if (d.class != @intFromEnum(device_abi.DeviceClass.pci_host_bridge)) continue;
        bridges += 1;
        var has_bus_range = false;
        var has_io = false;
        var memory_windows: u32 = 0;
        for (d.resources[0..@intCast(d.resource_count)]) |resource| {
            if (resource.kind == @intFromEnum(device_abi.ResourceKind.bus_range)) has_bus_range = true;
            if (resource.kind == @intFromEnum(device_abi.ResourceKind.io_port)) has_io = true;
            if (resource.kind == @intFromEnum(device_abi.ResourceKind.memory)) memory_windows += 1;
        }
        // ECAM plus at least one MMIO aperture, the bus range, the I/O window.
        if (has_bus_range and has_io and memory_windows >= 2) bridge_shape_ok = true;
    }
    check("a PCI host bridge was seeded (MCFG)", bridges >= 1);
    check("the bridge carries ECAM, apertures, bus range, and the I/O window", bridge_shape_ok);

    // M19.0: every PCI memory resource (config slice and BARs alike) must be
    // contained in one of its parent bridge's windows — the aperture derivation
    // from the memory map is what makes a future user-space device_register of
    // these functions pass containment. This is the assert that catches a
    // too-coarse hole computation before M19.2 would.
    var bars_contained = true;
    for (buffer[0..n]) |d| {
        if (d.class != @intFromEnum(device_abi.DeviceClass.pci_device)) continue;
        if (d.parent >= n) {
            bars_contained = false;
            continue;
        }
        const bridge = buffer[@intCast(d.parent)];
        for (d.resources[0..@intCast(d.resource_count)]) |r| {
            if (r.kind != @intFromEnum(device_abi.ResourceKind.memory)) continue;
            var inside = false;
            for (bridge.resources[0..@intCast(bridge.resource_count)]) |w| {
                if (w.kind != @intFromEnum(device_abi.ResourceKind.memory)) continue;
                if (r.start >= w.start and r.start + r.len <= w.start + w.len) inside = true;
            }
            if (!inside) {
                bars_contained = false;
                log("  escaping BAR: 0x{x}+0x{x} on device {d}\n", .{ r.start, r.len, d.id });
            }
        }
    }
    check("every PCI BAR lies inside a bridge aperture (M19.0)", bars_contained);

    result();
}

/// Audit the W^X invariant across the memory classes: kernel code must be
/// executable, everything else must not be. `architecture.pageExecutable` reads the leaf
/// page-table entry's NX bit, so this guards the permission overlay in paging.zig —
/// a broader check than `fault-nx`, which only exercises one data page.
fn wxTest() void {
    log("DANOS-TEST-BEGIN: wx\n", .{});

    check("kernel code is executable (R+X)", architecture.pageExecutable(@intFromPtr(&wxTest)));

    const ro = "danos-wx-probe"; // string literal -> .rodata
    check("rodata is non-executable (NX)", !architecture.pageExecutable(@intFromPtr(ro.ptr)));

    check("kernel data is non-executable (NX)", !architecture.pageExecutable(@intFromPtr(&passed)));

    if (heap.allocator().alloc(u8, 64) catch null) |h| {
        check("heap is non-executable (NX)", !architecture.pageExecutable(@intFromPtr(h.ptr)));
        heap.allocator().free(h);
    }

    var local: u64 = 0;
    _ = &local;
    check("stack is non-executable (NX)", !architecture.pageExecutable(@intFromPtr(&local)));

    result();
}

/// Verify the on-demand VMM: map a fresh frame at an unused virtual address, and
/// check it's writable and reads back.
fn vmm() void {
    log("DANOS-TEST-BEGIN: vmm\n", .{});
    const frame = pmm.alloc();
    check("frame available to map", frame != null);
    if (frame) |physical| {
        var virtual: u64 = 0x0000_4000_0000_0000; // canonical, well clear of everything mapped
        architecture.mapPage(virtual, physical, true);
        const p: *volatile u64 = @ptrFromInt(virtual);
        p.* = 0xdead_c0de_cafe_babe;
        check("mapped page is writable and reads back", p.* == 0xdead_c0de_cafe_babe);
        architecture.unmapPage(virtual);
        pmm.free(physical);
        virtual += 0;
    }
    result();
}

/// Exercise the kernel heap: basic alloc/write/free, reuse, growth beyond the
/// initial region, and a std container backed by it.
fn heapTest() void {
    log("DANOS-TEST-BEGIN: heap\n", .{});
    const a = heap.allocator();

    // Allocate, write a pattern, read it back, free.
    const buffer = a.alloc(u8, 4096) catch null;
    check("alloc 4096 bytes", buffer != null);
    if (buffer) |b| {
        @memset(b, 0xAB);
        check("heap memory is writable and reads back", b[0] == 0xAB and b[4095] == 0xAB);
        a.free(b);
    }

    // Freeing then re-allocating the same size should reuse the block.
    const p1 = a.alloc(u64, 8) catch null;
    const address1 = if (p1) |p| @intFromPtr(p.ptr) else 0;
    if (p1) |p| a.free(p);
    const p2 = a.alloc(u64, 8) catch null;
    const address2 = if (p2) |p| @intFromPtr(p.ptr) else 0;
    check("freed block is reused", address1 != 0 and address1 == address2);
    if (p2) |p| a.free(p);

    // Force growth past the initial page and check every block is usable.
    var blocks: [64]?[]u8 = .{null} ** 64;
    var ok = true;
    for (&blocks, 0..) |*slot, i| {
        const b = a.alloc(u8, 4096) catch null;
        slot.* = b;
        if (b) |bb| @memset(bb, @intCast(i & 0xff)) else {
            ok = false;
        }
    }
    for (blocks, 0..) |slot, i| {
        if (slot) |bb| {
            if (bb[0] != @as(u8, @intCast(i & 0xff)) or bb[4095] != @as(u8, @intCast(i & 0xff))) ok = false;
        }
    }
    check("many allocations (heap growth) stay valid", ok);
    for (blocks) |slot| {
        if (slot) |bb| a.free(bb);
    }

    // A std container backed by the kernel heap.
    var list: std.ArrayList(u32) = .empty;
    var sum: u64 = 0;
    var expected: u64 = 0;
    var i: u32 = 0;
    var list_ok = true;
    while (i < 1000) : (i += 1) {
        list.append(a, i) catch {
            list_ok = false;
        };
        expected += i;
    }
    for (list.items) |v| sum += v;
    list.deinit(a);
    check("std.ArrayList on the kernel heap", list_ok and sum == expected);

    result();
}

/// Verify the calibrated clocks: sane measured frequencies, monotonic uptime that
/// advances with real ticks, and — the point of the TSC clock — nanosecond
/// resolution far finer than the 1 ms tick, with the unit functions consistent.
fn wallClock() void {
    log("DANOS-TEST-BEGIN: wall-clock\n", .{});
    // The RTC was read and anchored at boot (kmain -> wall_clock.init()).
    const seconds = wall_clock.nowSeconds();
    log("  epoch: {d}\n", .{seconds});
    // A plausible current wall-clock: after 2020-01-01 (1577836800) and before 2050
    // (2524608000) — catches a broken CMOS read or a wrong epoch conversion.
    check("wall clock reads a plausible current epoch", seconds > 1_577_836_800 and seconds < 2_524_608_000);
    result();
}

fn clock() void {
    log("DANOS-TEST-BEGIN: clock\n", .{});

    const timer_clock = architecture.timerClockHz();
    check("timer clock frequency measured", timer_clock > 1_000_000 and timer_clock < 100_000_000_000);
    const clock_hz = architecture.clockHz();
    check("monotonic clock frequency measured", clock_hz > 100_000_000 and clock_hz < 100_000_000_000);

    // Uptime advances over ~5 real ticks (1000 Hz => 1 tick == 1 ms).
    const start_ticks = architecture.ticks();
    const start_ms = architecture.millis();
    var spins: u64 = 0;
    while (architecture.ticks() < start_ticks + 5 and spins < 5_000_000_000) spins +%= 1;
    const elapsed_ms = architecture.millis() - start_ms;
    check("uptime advances with ticks", elapsed_ms >= 5 and elapsed_ms < 100);

    // Sub-millisecond resolution: spin until nanos() first advances, then confirm
    // that first step happened within a millisecond — so nanos() resolves finer
    // than the 1 ms tick (a tick clock's smallest step *is* 1 ms). Spinning to the
    // first change is robust to QEMU's coarse TSC update granularity.
    const n1 = architecture.nanos();
    var s2: u64 = 0;
    while (architecture.nanos() == n1 and s2 < 10_000_000) s2 +%= 1;
    const n2 = architecture.nanos();
    check("nanos() has sub-millisecond resolution", n2 > n1 and (n2 - n1) < 1_000_000);

    // The unit functions agree (within rounding).
    const ns = architecture.nanos();
    check("nanos/micros/millis are consistent", diffWithin(architecture.micros(), ns / 1000, 1000) and diffWithin(architecture.millis(), ns / 1_000_000, 2));

    result();
}

fn diffWithin(a: u64, b: u64, tol: u64) bool {
    return if (a > b) a - b <= tol else b - a <= tol;
}

// --- scheduler tests ------------------------------------------------------

var counters = [_]u64{0} ** 3;

fn spin0() void {
    const p: *volatile u64 = &counters[0];
    while (true) p.* = p.* +% 1;
}
fn spin1() void {
    const p: *volatile u64 = &counters[1];
    while (true) p.* = p.* +% 1;
}
fn spin2() void {
    const p: *volatile u64 = &counters[2];
    while (true) p.* = p.* +% 1;
}

/// Preemption: spawn three tasks that busy-loop *without* yielding. If they all
/// make progress, the timer must be preempting between them (and the context
/// switch works) — because nothing yields voluntarily.
fn schedulerTest() void {
    log("DANOS-TEST-BEGIN: sched\n", .{});
    counters = .{ 0, 0, 0 };
    scheduler.spawn(spin0, 4);
    scheduler.spawn(spin1, 4);
    scheduler.spawn(spin2, 4);

    const c0: *volatile u64 = &counters[0];
    const c1: *volatile u64 = &counters[1];
    const c2: *volatile u64 = &counters[2];
    var spins: u64 = 0;
    while ((c0.* == 0 or c1.* == 0 or c2.* == 0) and spins < 5_000_000_000) spins +%= 1;

    check("all three non-yielding tasks made progress (preemption)", c0.* > 0 and c1.* > 0 and c2.* > 0);
    result();
}

var run_order = [_]u8{0} ** 4;
var run_n: usize = 0;

fn recordExit(priority: u8) void {
    run_order[run_n] = priority;
    run_n += 1;
    scheduler.exit();
}
fn taskHigh() void {
    recordExit(6);
}
fn taskMid() void {
    recordExit(4);
}
fn taskLow() void {
    recordExit(2);
}

/// Fixed priority: with preemption off (deterministic), spawn tasks at three
/// priorities and let them run cooperatively. They must run highest-first.
fn priorityTest() void {
    log("DANOS-TEST-BEGIN: priority\n", .{});
    scheduler.setPreemption(false);
    scheduler.setPriority(1); // above the idle task (0), below the workers — runs last
    run_n = 0;

    scheduler.spawn(taskLow, 2);
    scheduler.spawn(taskMid, 4);
    scheduler.spawn(taskHigh, 6);

    while (run_n < 3) scheduler.yield(); // regain control only once the workers are done

    check("tasks ran highest-priority first", run_order[0] == 6 and run_order[1] == 4 and run_order[2] == 2);

    scheduler.setPriority(4);
    scheduler.setPreemption(true);
    result();
}

var event_wait_queue: scheduler.WaitQueue = .{};
var event_stage: u32 = 0;

fn eventWaiter() void {
    event_stage = 1; // reached the wait
    scheduler.wait(&event_wait_queue); // block until woken
    event_stage = 3; // woken and resumed
    scheduler.exit();
}

/// Event-based blocking: a task blocks on a wait queue and is woken. The waiter is
/// higher priority, so waking it preempts us and it runs to completion at once.
fn eventTest() void {
    log("DANOS-TEST-BEGIN: event\n", .{});
    event_stage = 0;
    scheduler.spawn(eventWaiter, 6); // higher priority than this task (4)

    var spins: u64 = 0;
    while (event_stage != 1 and spins < 1_000_000_000) : (spins += 1) scheduler.yield();
    check("waiter reached the wait and blocked", event_stage == 1);

    scheduler.wake(&event_wait_queue);
    check("wake resumed the blocked waiter (preempting)", event_stage == 3);
    result();
}

var channel: ipc.Channel(u64, 4) = .{};
var receive_sum: u64 = 0;
var receive_count: u64 = 0;

fn producer() void {
    var i: u64 = 1;
    while (i <= 100) : (i += 1) channel.send(i);
    scheduler.exit();
}
fn consumer() void {
    var n: u64 = 0;
    while (n < 100) : (n += 1) {
        receive_sum += channel.receive();
        receive_count += 1;
    }
    scheduler.exit();
}

/// IPC: a producer and consumer pass 100 messages through a 4-slot channel. The
/// small buffer forces the channel full and empty repeatedly, exercising both the
/// blocking-send and blocking-receive paths. The messages must arrive intact.
fn ipcTest() void {
    log("DANOS-TEST-BEGIN: ipc\n", .{});
    channel = .{};
    receive_sum = 0;
    receive_count = 0;
    scheduler.spawn(consumer, 5); // above this task (4) so they run and we observe after
    scheduler.spawn(producer, 5);

    var spins: u64 = 0;
    while (receive_count < 100 and spins < 2_000_000_000) : (spins += 1) scheduler.yield();

    check("all 100 messages received", receive_count == 100);
    check("messages arrived intact (sum 1..100 == 5050)", receive_sum == 5050);
    result();
}

/// Blocking: sleep(50) should block this task for about 50 ms (measured on the
/// calibrated clock) — not busy-wait — while the idle task runs.
fn sleepTest() void {
    log("DANOS-TEST-BEGIN: sleep\n", .{});
    const t0 = architecture.millis();
    scheduler.sleep(50);
    const elapsed = architecture.millis() - t0;
    check("sleep(50) blocked for ~50 ms", elapsed >= 50 and elapsed <= 70);
    result();
}

// --- SMP parallelism ------------------------------------------------------

var seen_core = [_]bool{false} ** 8;
var smp_running: bool = true;

/// A worker that, while running, records which core it's executing on. Spread across
/// spawned workers and idle APs, these should land on more than one core.
fn smpWorker() void {
    const p: *volatile bool = &smp_running;
    while (p.*) {
        const c = scheduler.currentCpuIndex();
        if (c < seen_core.len) seen_core[c] = true;
    }
    scheduler.exit();
}

/// Prove tasks run **in parallel** on multiple cores (not just interleaved on one).
/// Spawn several CPU-bound workers; each stamps the core it runs on into `seen_core`.
/// With the application processors online, more than one core should show up — which
/// can only happen if work is genuinely running at the same time on different cores.
/// (Run with QEMU `-smp N`; on a single core this would see just one and fail.)
fn smpTest() void {
    log("DANOS-TEST-BEGIN: smp\n", .{});
    seen_core = .{false} ** 8;
    smp_running = true;

    var i: usize = 0;
    while (i < 4) : (i += 1) scheduler.spawn(smpWorker, 4);

    // Let the workers run across cores for a stretch of real time.
    var spins: u64 = 0;
    while (spins < 2_000_000_000) spins +%= 1;
    smp_running = false;

    var cores_seen: u32 = 0;
    for (seen_core) |s| {
        if (s) cores_seen += 1;
    }
    log("DANOS-SMP: workers ran on {d} distinct core(s)\n", .{cores_seen});
    check("tasks ran on multiple cores in parallel", cores_seen >= 2);

    // Bring-up is done, so the trampoline frame must be inert: zeroed (no stale code)
    // and non-executable (W^X restored). It's armed only while a core is climbing.
    const tramp = architecture.trampolinePage();
    check("trampoline frame reserved", tramp != 0);
    if (tramp != 0) {
        const bytes: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(tramp));
        var zeroed = true;
        for (0..4096) |b| {
            if (bytes[b] != 0) zeroed = false;
        }
        check("trampoline page zeroed when dormant", zeroed);
        check("trampoline page non-executable when dormant", !architecture.pageExecutable(tramp));
    }
    result();
}

// --- affinity: a pinned task never migrates -------------------------------

var affinity_cores = [_]bool{false} ** 8;
var affinity_running: bool = true;

fn affinityWorker() void {
    const p: *volatile bool = &affinity_running;
    while (p.*) {
        const c = scheduler.currentCpuIndex();
        if (c < affinity_cores.len) affinity_cores[c] = true;
    }
    scheduler.exit();
}

/// A task pinned to a core must run **only** on that core. Pin a busy worker to
/// core 1 and let it run through many preemptions; it must have stamped core 1 and no
/// other. An *unpinned* task scatters across cores (that's what the smp test shows),
/// so a broken pin fails this deterministically — over this many time slices a
/// free-floating task will land on some other core.
fn affinityTest() void {
    log("DANOS-TEST-BEGIN: affinity\n", .{});
    affinity_cores = .{false} ** 8;
    affinity_running = true;

    if (!scheduler.spawnOn(affinityWorker, 4, 1)) {
        check("worker pinned to core 1 (run with -smp)", false);
        result();
        return;
    }

    // Let many time slices pass so the scheduler runs the pinned worker across ticks.
    // Wait on the wall clock, not a raw iteration count: a fixed-count busy-loop's
    // wall-time is a codegen lottery (the optimiser may elide or vectorise it), so an
    // unrelated change elsewhere in this file could swing this test from ~4 s to ~50 s.
    const run_until = architecture.millis() + 400;
    while (architecture.millis() < run_until) {}
    affinity_running = false;
    const settle_until = architecture.millis() + 50;
    while (architecture.millis() < settle_until) {} // let the worker see the flag and exit

    var others: u32 = 0;
    for (affinity_cores, 0..) |seen, c| {
        if (seen and c != 1) others += 1;
    }
    log("DANOS-AFFINITY: pinned worker touched core 1={}, other cores={d}\n", .{ affinity_cores[1], others });
    check("pinned task ran on its core (1)", affinity_cores[1]);
    check("pinned task never migrated to another core", others == 0);
    result();
}

// --- SMP stress: hammer the big kernel lock across cores ------------------

const stress_pairs = 4; // producer/consumer pairs (8 tasks; fits the 16-task pool)
const stress_msgs = 100_000; // messages per pair
const stress_cap = 4; // small channel -> constant block/wake, more lock churn

var stress_chan = [_]ipc.Channel(u64, stress_cap){.{}} ** stress_pairs;
var stress_receive = [_]u64{0} ** stress_pairs; // messages received per pair
var stress_order_ok = [_]bool{true} ** stress_pairs; // FIFO order held per pair
var stress_cores = [_]bool{false} ** 8; // cores that ran a consumer
var stress_prod_claim: usize = 0;
var stress_cons_claim: usize = 0;

fn stressProducer() void {
    // Claim a unique pair index (atomic: producers start on different cores).
    const index = @atomicRmw(usize, &stress_prod_claim, .Add, 1, .monotonic);
    var v: u64 = 1;
    while (v <= stress_msgs) : (v += 1) stress_chan[index].send(v);
    scheduler.exit();
}

fn stressConsumer() void {
    const index = @atomicRmw(usize, &stress_cons_claim, .Add, 1, .monotonic);
    var expected: u64 = 1;
    while (expected <= stress_msgs) : (expected += 1) {
        const got = stress_chan[index].receive();
        if (got != expected) stress_order_ok[index] = false; // lost/reordered => lock broke
        const c = scheduler.currentCpuIndex();
        if (c < stress_cores.len) stress_cores[c] = true;
        stress_receive[index] = expected;
    }
    scheduler.exit();
}

/// Stress the big kernel lock under sustained cross-core contention. Each pair drives
/// `stress_msgs` sequenced messages through a 4-slot channel — every send and receive
/// takes the lock, and the small buffer forces constant block/wake (so the scheduler
/// churns too). A single-producer/single-consumer channel must deliver in strict FIFO
/// order; if the lock let two cores into a critical section at once, the ring buffer
/// corrupts and the consumer sees a wrong or out-of-order value (or the run hangs /
/// faults). Passing means ~320k lock acquisitions across the cores stayed consistent.
fn stressTest() void {
    log("DANOS-TEST-BEGIN: smp-stress\n", .{});
    stress_chan = [_]ipc.Channel(u64, stress_cap){.{}} ** stress_pairs;
    stress_receive = [_]u64{0} ** stress_pairs;
    stress_order_ok = [_]bool{true} ** stress_pairs;
    stress_cores = [_]bool{false} ** 8;
    stress_prod_claim = 0;
    stress_cons_claim = 0;

    var i: usize = 0;
    while (i < stress_pairs) : (i += 1) scheduler.spawn(stressConsumer, 4);
    i = 0;
    while (i < stress_pairs) : (i += 1) scheduler.spawn(stressProducer, 4);

    // Drop below the workers so they get the cores; wake periodically to check for
    // completion. A broken lock instead hangs here (harness timeout) or faults.
    scheduler.setPriority(1);
    var spins: u64 = 0;
    while (spins < 40_000_000_000) : (spins += 1) {
        var done = true;
        for (stress_receive) |n| {
            if (n < stress_msgs) done = false;
        }
        if (done) break;
    }
    scheduler.setPriority(4);

    var total: u64 = 0;
    for (stress_receive) |n| total += n;
    var order_ok = true;
    for (stress_order_ok) |ok| {
        if (!ok) order_ok = false;
    }
    var cores: u32 = 0;
    for (stress_cores) |s| {
        if (s) cores += 1;
    }

    log("DANOS-STRESS: {d}/{d} pairs complete on {d} cores\n", .{ total, @as(u64, stress_pairs) * stress_msgs, cores });
    check("every message delivered", total == @as(u64, stress_pairs) * stress_msgs);
    check("strict FIFO order held (no lock corruption)", order_ok);
    check("contention was genuinely cross-core", cores >= 2);
    result();
}

/// Retry: `main` forced the first AP wake attempt to fail (architecture.testFailNextWakes),
/// so a core missed its first INIT-SIPI-SIPI. The boot retry must have brought it back
/// anyway — every enumerated core should be online. If retry were broken, that core
/// would be parked and the count would fall short.
fn smpRetryTest() void {
    log("DANOS-TEST-BEGIN: smp-retry\n", .{});
    const total = platform.cpus().len;
    const online = scheduler.onlineCount();
    log("DANOS-RETRY: {d}/{d} cores online after a forced first-wake failure\n", .{ online, total });
    check("multiple cores enumerated (run with -smp)", total >= 2);
    check("retry brought every core online despite a failed first wake", online == total);
    result();
}

// --- ring 3 (user mode) -----------------------------------------------------

/// The mmap/munmap grant path: create a fresh address space, hand out pages into
/// its mmap arena the way the `mmap` system_call does, prove they are real (write and
/// read them back through the physmap), then release them via the `munmap` path
/// (translate -> unmap -> free) and tear the address space down. The frame count
/// must return exactly to where it started — a leak or a double-free would show
/// as drift. Exercises the new `translate`/`unmapUserPageInto` primitives that
/// back munmap, without needing a user binary that calls the syscalls.
fn userMemTest() void {
    log("DANOS-TEST-BEGIN: usermem\n", .{});
    const base_free = pmm.stats().free_frames;

    const address_space = architecture.createAddressSpace() orelse {
        check("created a fresh address space", false);
        result();
        return;
    };
    check("created a fresh address space", address_space != 0);

    // Grant three pages into the arena, mapped RW + NX (the mmap contract).
    const npages = 3;
    const arena = process.heap_arena_base;
    var frames: [npages]u64 = undefined;
    var mapped: usize = 0;
    while (mapped < npages) : (mapped += 1) {
        frames[mapped] = pmm.alloc() orelse break;
        architecture.mapUserPageInto(address_space, arena + mapped * abi.page_size, frames[mapped], true, false);
    }
    check("granted three user pages", mapped == npages);

    // Each page resolves back to the frame we mapped, and is writable RAM.
    var translate_ok = true;
    var rw_ok = true;
    for (0..npages) |i| {
        const va = arena + i * abi.page_size;
        const physical = architecture.translate(address_space, va) orelse {
            translate_ok = false;
            continue;
        };
        if (physical != frames[i]) translate_ok = false;
        const p: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(physical));
        p[0] = 0xA5;
        if (p[0] != 0xA5) rw_ok = false;
    }
    check("translate resolves each grant to its frame", translate_ok);
    check("granted pages are writable RAM", rw_ok);

    // Release them the way munmap does, then tear down the address space.
    for (0..npages) |i| {
        const va = arena + i * abi.page_size;
        if (architecture.translate(address_space, va)) |physical| {
            architecture.unmapUserPageInto(address_space, va);
            pmm.free(physical);
        }
    }
    check("munmap unmapped every grant", architecture.translate(address_space, arena) == null);
    architecture.destroyAddressSpace(address_space);

    check("no frames leaked (free count restored)", pmm.stats().free_frames == base_free);
    result();
}

// --- synchronous IPC --------------------------------------------------------

var ipc_endpoint: *ipcsync.Endpoint = undefined;
var ipc_replies_ok: bool = false;
var ipc_done: bool = false;

/// Echo-increment server: reply to each request with request+1, forever.
fn ipcServer() void {
    var reply_buffer: [8]u8 = undefined;
    var reply_len: u64 = 0;
    var badge: u64 = 0;
    var received_cap: u64 = abi.no_cap;
    while (true) {
        var receive: [8]u8 = undefined;
        const n = ipcsync.replyWait(ipc_endpoint, @intFromPtr(&reply_buffer), reply_len, @intFromPtr(&receive), receive.len, abi.no_cap, &badge, &received_cap);
        if (n < 0) scheduler.exit();
        const v = std.mem.readInt(u64, receive[0..8], .little);
        std.mem.writeInt(u64, reply_buffer[0..8], v + 1, .little);
        reply_len = 8;
    }
}

/// Client: make 100 synchronous calls, checking every reply is request+1.
fn ipcClient() void {
    var ok = true;
    var i: u64 = 0;
    while (i < 100) : (i += 1) {
        var message: [8]u8 = undefined;
        std.mem.writeInt(u64, message[0..8], i, .little);
        var reply: [8]u8 = undefined;
        var received_cap: u64 = abi.no_cap;
        const n = ipcsync.call(ipc_endpoint, @intFromPtr(&message), 8, @intFromPtr(&reply), reply.len, abi.no_cap, &received_cap);
        if (n != 8 or std.mem.readInt(u64, reply[0..8], .little) != i + 1) ok = false;
    }
    ipc_replies_ok = ok;
    ipc_done = true;
    scheduler.exit();
}

/// Synchronous IPC: a client and a server (two kernel tasks) ping-pong 100 calls
/// through one Endpoint. Each round exercises the full rendezvous — the client
/// blocks in `call`, the server blocks in `replyWait`, the reply is routed back to
/// the exact caller, and `copyAcross` moves the bytes — so 100 correct replies
/// prove the block/wake and reply-routing paths. (Kernel tasks, so no user ELF.)
fn ipcCallTest() void {
    log("DANOS-TEST-BEGIN: ipc-call\n", .{});
    ipc_endpoint = ipcsync.createIpcEndpoint().?;
    ipc_replies_ok = false;
    ipc_done = false;
    scheduler.spawn(ipcServer, 5); // above this task, so the workers run
    scheduler.spawn(ipcClient, 5);

    const done: *volatile bool = &ipc_done;
    var spins: u64 = 0;
    while (!done.* and spins < 100_000_000) : (spins += 1) scheduler.yield();

    check("client completed 100 synchronous calls", ipc_done);
    check("every reply was request+1 (rendezvous + reply routing intact)", ipc_replies_ok);
    result();
}

// --- IPC capability passing (M13) -------------------------------------------

var cap_endpoint: *ipcsync.Endpoint = undefined;
var cap_ep_x: *ipcsync.Endpoint = undefined; // client mints, sends to the server
var cap_ep_y: *ipcsync.Endpoint = undefined; // server mints, sends back to the client
var cap_server_got_x: bool = false;
var cap_client_got_y: bool = false;
var cap_done: bool = false;

/// Server half of the "open" pattern: receive one request carrying a capability,
/// verify it, then reply handing back a capability of its own.
fn capServer() void {
    const me = scheduler.current();
    cap_ep_y = ipcsync.createIpcEndpoint().?;
    const h_y = ipcsync.installHandle(me, cap_ep_y); // the handle to send back in the reply

    var reply_buffer: [8]u8 = .{0} ** 8;
    var receive: [8]u8 = undefined;
    var badge: u64 = 0;
    var received: u64 = abi.no_cap;

    // Phase 1: no reply owed yet — receive the client's request, which carries ep_x.
    _ = ipcsync.replyWait(cap_endpoint, @intFromPtr(&reply_buffer), 0, @intFromPtr(&receive), receive.len, abi.no_cap, &badge, &received);
    cap_server_got_x = received != abi.no_cap and
        ipcsync.resolveHandle(me, received) == cap_ep_x and
        cap_ep_x.refcount == 2; // shared (client's handle + this one), not moved

    // Phase 2: reply to the held client, handing it ep_y; then block for a next
    // request that never comes (so this replyWait does not return).
    _ = ipcsync.replyWait(cap_endpoint, @intFromPtr(&reply_buffer), 8, @intFromPtr(&receive), receive.len, @intCast(h_y), &badge, &received);
    scheduler.exit();
}

/// Client half of "open": mint a capability, send it in a call, receive one back.
fn capClient() void {
    const me = scheduler.current();
    cap_ep_x = ipcsync.createIpcEndpoint().?;
    const h_x = ipcsync.installHandle(me, cap_ep_x);

    var message: [8]u8 = .{0} ** 8;
    var reply: [8]u8 = undefined;
    var received: u64 = abi.no_cap;
    _ = ipcsync.call(cap_endpoint, @intFromPtr(&message), 8, @intFromPtr(&reply), reply.len, @intCast(h_x), &received);
    cap_client_got_y = received != abi.no_cap and
        ipcsync.resolveHandle(me, received) == cap_ep_y and
        cap_ep_y.refcount == 2;

    cap_done = true;
    scheduler.exit();
}

/// IPC capability passing: a client hands the server an endpoint in a `call`, and the
/// server hands one back in its reply — the primitive that lets a bus driver give a
/// class driver a private channel to one device (M13). Two kernel tasks (no user ELF);
/// each verifies the endpoint it received is the *same* object the peer sent (resolves
/// equal) and was *shared*, not moved (refcount bumped to 2).
fn capabilityTest() void {
    log("DANOS-TEST-BEGIN: ipc-cap\n", .{});
    cap_endpoint = ipcsync.createIpcEndpoint().?;
    cap_server_got_x = false;
    cap_client_got_y = false;
    cap_done = false;
    scheduler.spawn(capServer, 5);
    scheduler.spawn(capClient, 5);

    const done: *volatile bool = &cap_done;
    var spins: u64 = 0;
    while (!done.* and spins < 100_000_000) : (spins += 1) scheduler.yield();

    check("capability test completed", cap_done);
    check("server received the client's endpoint (same object, shared not moved)", cap_server_got_x);
    check("client received the server's endpoint back (same object, shared not moved)", cap_client_got_y);
    result();
}

/// DMA memory (M14): the properties a bus-mastering driver needs — physically
/// contiguous, a known physical address, correct cacheability, pinned, and reclaimed
/// on teardown. Exercises the kernel mechanism directly (`pmm.allocContiguous` +
/// `mapUserDmaInto`); the `dma_alloc`/`dma_free` syscalls are thin wrappers over it,
/// following the tested `mmap`/`mmio_map` shape, and land their first real use with the
/// first DMA driver.
fn dmaTest() void {
    log("DANOS-TEST-BEGIN: dma\n", .{});
    const base_free = pmm.stats().free_frames;

    // A contiguous run: aligned, and it consumed exactly that many frames.
    const frames = 4;
    const phys = pmm.allocContiguous(frames, ~@as(u64, 0)) orelse {
        check("allocContiguous(4) succeeded", false);
        result();
        return;
    };
    check("contiguous run is page-aligned", phys % abi.page_size == 0);
    check("contiguous run consumed 4 frames", pmm.stats().free_frames == base_free - frames);

    // The below-4G cap is honoured (legacy 32-bit DMA engines).
    const low = pmm.allocContiguous(2, @as(u64, 4) << 30) orelse 0;
    check("below-4G run stays under 4 GiB", low != 0 and low + 2 * abi.page_size <= (@as(u64, 4) << 30));

    // Map the run into a fresh address space as coherent DMA and translate each page
    // back: the same physical run, in order — proving contiguity and the mapping.
    const address_space = architecture.createAddressSpace().?;
    architecture.mapUserDmaInto(address_space, process.dma_arena_base, phys, frames * abi.page_size);
    var mapped_ok = true;
    for (0..frames) |i| {
        const va = process.dma_arena_base + i * abi.page_size;
        const got = architecture.translate(address_space, va) orelse {
            mapped_ok = false;
            break;
        };
        if (got != phys + i * abi.page_size) mapped_ok = false;
    }
    check("DMA pages translate to the contiguous physical run", mapped_ok);

    // Teardown must reclaim the DMA RAM (the leaves carry no device_grant, so
    // freeSubtree frees them as ordinary frames) — a driver that just dies leaks none.
    architecture.destroyAddressSpace(address_space);
    for (0..2) |i| pmm.free(low + i * abi.page_size);
    check("no frames leaked after DMA teardown", pmm.stats().free_frames == base_free);
    result();
}

/// MSI (M15): a per-device, edge-triggered interrupt vector delivered as an IPC
/// notification. QEMU's HPET has no MSI, so this exercises the vector-routing path with
/// a self-IPI standing in for the device's MSI memory write — proving the kernel
/// allocates a vector, `dispatch` recognises it as MSI (EOI + notify, no mask cycle),
/// and the bound endpoint is notified. The `msi_bind` syscall wraps `irq.msiBind` with
/// the device-claim check and lands its first real use with the first PCI driver.
fn msiTest() void {
    log("DANOS-TEST-BEGIN: msi\n", .{});
    const endpoint = ipcsync.createIpcEndpoint().?;

    const flags = sync.enter();
    const vector = irq.msiBind(endpoint, 0) catch {
        sync.leave(flags);
        check("msiBind allocated a vector", false);
        result();
        return;
    };
    sync.leave(flags);
    check("msiBind allocated a vector in the device window", vector >= architecture.irq_vector_base and
        vector < architecture.irq_vector_base + architecture.irq_vector_count);

    // Fire the vector — the stand-in for the device writing its MSI (address, data).
    const tail: *volatile u8 = &endpoint.notify_tail;
    const before = tail.*;
    architecture.selfIpi(vector);
    var spins: u64 = 0;
    while (tail.* == before and spins < 100_000_000) : (spins += 1) scheduler.yield();
    check("self-IPI at the MSI vector notified the bound endpoint", tail.* != before);
    result();
}

/// IOMMU (M16): with an emulated VT-d unit present (the harness boots this case with
/// `-device intel-iommu`), danos must find it in the ACPI DMAR table, map its register
/// block, and read back a real version. This is *detection*, the honest first step —
/// no translation domains are programmed yet, so DMA is still unprotected; enforcement
/// lands with the first DMA driver (docs/driver-model.md M16).
fn iommuTest() void {
    log("DANOS-TEST-BEGIN: iommu\n", .{});
    const pinfo = platform.platformInformation();
    check("IOMMU found in the DMAR table", pinfo.iommu_present);
    check("VT-d unit has a register base", pinfo.iommu_base != 0);
    check("VT-d version register reads back nonzero (real, mappable unit)", pinfo.iommu_version != 0);
    log("DANOS-IOMMU: base=0x{x} version=0x{x} capabilities=0x{x}\n", .{ pinfo.iommu_base, pinfo.iommu_version, pinfo.iommu_capabilities });
    result();
}

/// Port I/O grants: ring 3 has no `in`/`out`, so a legacy driver reaches its ports
/// through `io_read`/`io_write`, gated by `device_claim` and the device's `io_port`
/// resource exactly like `mmio_map` gates memory. Target the PS/2 controller's status
/// port (0x64) — discovered on every PC and side-effect-free to read. Proves the
/// capability gate (`resolveIoPort` admits an in-range access, refuses out-of-range,
/// over-wide, and unclaimed) and that the kernel actually performs the `in`. The
/// `io_read`/`io_write` syscalls wrap this with the same ring-3 dispatch every device
/// driver already uses.
fn ioPortTest() void {
    log("DANOS-TEST-BEGIN: ioport\n", .{});
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    const n = @min(devices_broker.enumerate(&buffer), buffer.len);

    // Post-M20.3 the PS/2 node is registered at runtime by the ring-3 acpi
    // service, so it is absent from this boot snapshot. Exercise the same
    // io_port claim/resolve mechanism against the acpi-tables node's broad I/O
    // grant — the window that now carries port authority (the service uses it
    // for exactly this). The PS/2 status port 0x64 is offset 0x64 within it.
    var found_id: ?u64 = null;
    var found_res: u64 = 0;
    outer: for (buffer[0..n]) |d| {
        if (d.class != @intFromEnum(device_abi.DeviceClass.acpi_tables)) continue;
        for (0..d.resource_count) |ri| {
            const r = d.resources[ri];
            if (r.kind == @intFromEnum(device_abi.ResourceKind.io_port) and r.start == 0 and r.len > 0x64) {
                found_id = d.id;
                found_res = ri;
                break :outer;
            }
        }
    }
    const id = found_id orelse {
        check("discovered the acpi-tables I/O window", false);
        result();
        return;
    };
    check("discovered the acpi-tables I/O window", true);

    const me = scheduler.current();
    check("claimed the io_port device", devices_broker.claim(id, me.id));
    check("an in-range access resolves to port 0x64", process.resolveIoPort(me, id, found_res, 0x64, 1) == 0x64);
    check("a 4-byte access at the last port is refused", process.resolveIoPort(me, id, found_res, 0xFFFF, 4) == null);
    check("an out-of-range offset is refused", process.resolveIoPort(me, id, found_res, 0x10000, 1) == null);
    check("an unclaimed device id is refused", process.resolveIoPort(me, 0xDEAD_BEEF, found_res, 0x64, 1) == null);

    // The kernel actually issues the `in`. Reaching this line at all proves it didn't
    // fault; a width-1 read must return a single byte.
    const status = architecture.pioRead(1, 0x64);
    check("reading the PS/2 status port returned a byte", status <= 0xFF);
    log("DANOS-IOPORT: PS/2 status = 0x{x}\n", .{status});
    result();
}

/// The monotonic clock (the source `clock()` surfaces to user space). It must be
/// calibrated, move forward over a spin, and never run backwards — the guarantees a
/// driver's deadline timeout depends on. The syscall is a thin wrapper over this same
/// `architecture.nanos()`.
fn clockTest() void {
    log("DANOS-TEST-BEGIN: clock\n", .{});
    const t0 = architecture.nanos();
    check("monotonic clock is calibrated (nonzero)", t0 != 0);

    var last = t0;
    var monotonic = true;
    var advanced = false;
    var i: u32 = 0;
    while (i < 1_000_000) : (i += 1) {
        const t = architecture.nanos();
        if (t < last) monotonic = false;
        if (t > t0) advanced = true;
        last = t;
    }
    check("clock advanced over the spin", advanced);
    check("clock never ran backwards (monotonic)", monotonic);
    result();
}

/// The TSC clocksource + cross-core warp check (`-smp 4`). This is the real
/// Intel/AMD / KVM path — an invariant, synchronized TSC. TCG (the only x86
/// accelerator on an Apple-Silicon host) won't advertise an invariant TSC, so the
/// boot forces the TSC clocksource on (kernel.zig, gated on this case) to exercise
/// the machinery: the kernel must run the per-AP warp check as each core came up,
/// find the cores' TSCs synchronized, and keep the clock on the TSC (no HPET
/// fallback). The default suite (no force) exercises the HPET fallback instead.
fn tscSyncTest() void {
    log("DANOS-TEST-BEGIN: tsc-sync\n", .{});
    check("clocksource is the TSC (forced-invariant path)", eql(architecture.clockSourceName(), "tsc"));
    check("the cross-core warp check ran on the APs", architecture.warpChecksRun() >= 1);
    check("per-core TSCs synchronized (no warp, no HPET fallback)", architecture.clockSynchronized());

    // A warp that slipped past the bring-up check would surface as a backward reading.
    var last = architecture.nanos();
    var monotonic = true;
    var advanced = false;
    var i: u32 = 0;
    while (i < 1_000_000) : (i += 1) {
        const t = architecture.nanos();
        if (t < last) monotonic = false;
        if (t > last) advanced = true;
        last = t;
    }
    check("monotonic clock advanced", advanced);
    check("monotonic clock never ran backward", monotonic);
    result();
}

var proc_worker_run: bool = true;
var proc_worker_ran: bool = false;

/// A kernel task that spins while a process runs, to prove the two coexist under
/// preemption (a process on its own CR3 does not stall kernel work).
fn procWorker() void {
    const running: *volatile bool = &proc_worker_run;
    const ran: *volatile bool = &proc_worker_ran;
    while (running.*) ran.* = true;
    scheduler.exit();
}

/// Real processes: load /system/services/init as TWO scheduled ring-3 processes, each with
/// its own address space at the same virtual addresses, running concurrently
/// with a kernel task. Both must make heartbeat syscalls from CPL 3 — which can
/// only happen if each runs on its own page tables (CR3 switched correctly per
/// process) and preemption interleaves them with the kernel worker. This is the
/// strongest cheap proof of address-space isolation.
fn processTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: process\n", .{});
    const image = bundledInit(boot_information) orelse {
        check("initial_ramdisk carries /system/services/init", false);
        result();
        return;
    };
    check("initial_ramdisk carries /system/services/init", true);

    process.write_count = 0;
    process.write_from_user = false;
    proc_worker_run = true;
    proc_worker_ran = false;
    scheduler.spawn(procWorker, 4); // kernel task at the processes' priority

    var spawned: u32 = 0;
    if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
    if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}

    // Wait (real time) for several heartbeats across the two processes. Each
    // process sleeps ~1 s between beats, so a few seconds yields several.
    scheduler.setPriority(1); // drop below the workers so they get the cores
    const deadline = architecture.millis() + 8000;
    while (process.write_count < 4 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);
    proc_worker_run = false;

    log("DANOS-PROC: spawned {d} processes, {d} heartbeats\n", .{ spawned, process.write_count });
    check("both init processes spawned on their own address spaces", spawned == 2);
    check("processes made repeated heartbeat syscalls (>=4)", process.write_count >= 4);
    check("heartbeats came from user mode (CPL 3)", process.write_from_user);
    check("a kernel task coexisted with the processes (preemption)", proc_worker_ran);
    result();
}

/// Isolation: a ring-3 read of a kernel-only page (the LAPIC page — present,
/// supervisor) must page-fault with error code 0x5 (present | user) at the user
/// RIP. The fault report is the pass signal (matched by the harness); if the
/// read is somehow allowed the blob spins and the harness times out.
fn userPfTest() void {
    log("DANOS-TEST-BEGIN: user-pf\n", .{});
    scheduler.setPreemption(false);
    _ = process.run(process.pfBlob()) catch {};
    log("DANOS-TEST-RESULT: FAIL (user read of kernel memory did not fault)\n", .{});
}

/// Spawn a real scheduled ring-3 process whose body is the user-pf blob (a read of
/// a kernel-only page, then a spin), so its first instruction raises #PF. Built by
/// hand — address space, code page RO+X, stack page RW+NX — because the blob is a
/// raw code fragment, not an ELF `spawnProcess` could load. Returns false if any
/// allocation fails.
fn spawnFaultingProcess() ?u32 {
    const blob = process.pfBlob();
    const flags = sync.enter();
    defer sync.leave(flags);

    const address_space = architecture.createAddressSpace() orelse return null;
    const code_frame = pmm.alloc() orelse {
        architecture.destroyAddressSpace(address_space);
        return null;
    };
    // Fill through the physmap (the user mapping is read-only); pad with int3 so a
    // stray jump traps instead of sliding.
    const code: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(code_frame));
    @memset(code[0..abi.page_size], 0xCC);
    @memcpy(code[0..blob.len], blob);
    architecture.mapUserPageInto(address_space, process.code_virtual, code_frame, false, true); // RO + X

    const stack_frame = pmm.alloc() orelse {
        architecture.destroyAddressSpace(address_space); // frees code_frame too — it's mapped
        return null;
    };
    architecture.mapUserPageInto(address_space, process.stack_base_virtual, stack_frame, true, false); // RW + NX

    // Supervised by the calling test task, so exitReasonOf can read the verdict.
    const id = scheduler.spawnUserLocked(address_space, process.code_virtual, process.stack_base_virtual + abi.page_size, 0, 4, "fault-probe", scheduler.currentId(), null) orelse {
        architecture.destroyAddressSpace(address_space);
        return null;
    };
    return id;
}

/// Fault recovery (docs/resilience.md step 2): a scheduled ring-3 process that
/// faults must be killed — counted, resources reclaimed — while the rest of the
/// system keeps running. init heartbeats before and after the kill are the proof
/// the OS survived; the old behaviour (halt the core) would freeze the beat and
/// time the harness out.
fn faultRecoveryTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: fault-recovery\n", .{});
    const image = bundledInit(boot_information) orelse {
        check("initial_ramdisk carries /system/services/init", false);
        result();
        return;
    };
    check("initial_ramdisk carries /system/services/init", true);

    process.write_count = 0;
    process.fault_kill_count = 0;
    const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |_| false;
    check("init spawned as the surviving process", spawned);

    // A first heartbeat proves init runs before the fault.
    scheduler.setPriority(1); // drop below the processes so they get the core
    var deadline = architecture.millis() + 8000;
    while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);
    check("init heartbeat before the fault", process.write_count >= 1);

    const probe = spawnFaultingProcess() orelse 0;
    check("faulting process spawned", probe != 0);

    // The kill: the faulting process #PFs on its first instruction and the kernel
    // reaps it instead of halting.
    scheduler.setPriority(1);
    deadline = architecture.millis() + 5000;
    while (process.fault_kill_count < 1 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);
    check("faulting process was killed (not the machine)", process.fault_kill_count == 1);
    check("the probe's reason reads segmentation_fault", process.exitReasonOf(scheduler.currentId(), probe) == @intFromEnum(abi.ExitReason.segmentation_fault));

    // Life after the kill: init must keep beating on the same core.
    const beats_at_kill = process.write_count;
    scheduler.setPriority(1);
    deadline = architecture.millis() + 8000;
    while (process.write_count < beats_at_kill + 2 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);
    check("init kept heartbeating after the kill", process.write_count >= beats_at_kill + 2);
    result();
}

/// Address-space refcount (docs/threading-plan.md M1): every process holds exactly one
/// reference to its address space, released when it dies, so `destroyAddressSpace` runs
/// exactly once per space — no leak, no double-free. Spawn and kill several ring-3
/// processes (the faulting probe, reaped by the kernel) and confirm the count of live
/// address spaces returns to baseline while destructions advance by exactly that many.
/// This is the foundation threads (shared address spaces) build on: the refactor must be
/// invisible while every space still has exactly one task.
fn addressSpaceRefcountTest(boot_information: *const BootInformation) void {
    _ = boot_information;
    log("DANOS-TEST-BEGIN: address-space-refcount\n", .{});
    const base_live = scheduler.liveAddressSpaceCount();
    const base_destroyed = scheduler.addressSpaceDestroyCount();
    const rounds: u32 = 5;
    var killed: u32 = 0;
    var round: u32 = 0;
    while (round < rounds) : (round += 1) {
        process.fault_kill_count = 0;
        const probe = spawnFaultingProcess() orelse break;
        _ = probe;
        // Let the probe fault on its first instruction and be reaped.
        scheduler.setPriority(1);
        const deadline = architecture.millis() + 5000;
        while (process.fault_kill_count < 1 and architecture.millis() < deadline) scheduler.yield();
        scheduler.setPriority(4);
        if (process.fault_kill_count >= 1) killed += 1;
    }
    check("all probes spawned and were killed", killed == rounds);
    check("live address-space count returned to baseline", scheduler.liveAddressSpaceCount() == base_live);
    check("each address space destroyed exactly once", scheduler.addressSpaceDestroyCount() == base_destroyed + rounds);
    if (killed == rounds and scheduler.liveAddressSpaceCount() == base_live and
        scheduler.addressSpaceDestroyCount() == base_destroyed + rounds)
        log("address-space-refcount: spaces released to baseline ok\n", .{});
    result();
}

/// Thread spawn (docs/threading-plan.md M2): the `thread-test` service spawns a worker
/// thread that writes a shared global; the main thread, polling that memory, observes the
/// write — proving `runtime.Thread.spawn` started a task in the **same** address space
/// (a separate process could not touch it). The service's own marker is the verdict.
fn threadSpawnTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-spawn\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    check("thread-test spawned", spawnNamed(rd, "thread-test"));

    // Wait for the service's verdict marker (it polls shared memory the worker wrote).
    const ok_marker = "thread-test: child ran in shared address space ok";
    const fail_marker = "thread-test: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 12000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("a worker thread ran in the shared address space (shared write observed)", bufferHas(ok_marker));
    check("the thread path reported no failure", !bufferHas(fail_marker));
    result();
}

/// Thread join + parallelism (docs/threading-plan.md M3): `thread-test` in join mode
/// spawns N workers that each do K atomic increments on a shared counter and stamp the
/// core they ran on; it `join`s all N and asserts the total is exactly N*K (every worker
/// ran, join waited for each) and that >1 core was used (genuine parallelism), then a
/// detached worker proves `detach`. Its single verdict marker is the case result.
fn threadJoinTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-join\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    // Spawn thread-test in join mode (argv selects the mode).
    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "join" })) true else |_| false;
        break;
    }
    check("thread-test (join mode) spawned", started);

    const ok_marker = "thread-test: join ok";
    const fail_marker = "thread-test: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 15000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("N worker threads joined; counter exact (N*K) and >1 core used", bufferHas(ok_marker));
    check("no thread failure reported", !bufferHas(fail_marker));
    result();
}

/// Futex (docs/threading-plan.md M4): `thread-test` in futex mode has a waiter thread
/// block in `futex_wait` on a word; the main thread publishes the word and `futex_wake`s
/// it. The serial order `waiting → waking → woke` shows the kernel handoff (the waiter
/// parked and was woken, not spun), and a `timedWait` on an unwoken word reports a
/// timeout. The verdict marker is emitted only after both hold.
fn threadFutexTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-futex\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "futex" })) true else |_| false;
        break;
    }
    check("thread-test (futex mode) spawned", started);

    const ok_marker = "thread-futex: ok";
    const fail_marker = "thread-futex: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 15000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    // Only the freshest marker is checked here — the kernel's log ring buffer may have
    // evicted the earlier ones by now. The waiting/waking/woke ordering (the handoff
    // proof) is asserted against the full serial stream by the qemu case's regex; the
    // verdict marker is emitted by thread-test only after the wake AND the timeout hold.
    check("futex handoff + timeout completed (verdict reached, no failure)", bufferHas(ok_marker) and !bufferHas(fail_marker));
    result();
}

/// Mutex + Condition (docs/threading-plan.md M5): `thread-test` in mutex mode runs a
/// bounded producer/consumer — P producers and C consumers over one `Mutex` and two
/// `Condition`s move N unique items through a small ring. Every item is produced once;
/// if the lock and condition variables are correct under real cross-core contention,
/// the consumed checksum and tally match exactly (no lost or duplicated item, no
/// overrun). The verdict marker is emitted only when both match.
fn threadMutexTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-mutex\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "mutex" })) true else |_| false;
        break;
    }
    check("thread-test (mutex mode) spawned", started);

    const ok_marker = "thread-mutex: ok";
    const fail_marker = "thread-mutex: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 20000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("producer/consumer over Mutex+Condition moved every item exactly once", bufferHas(ok_marker) and !bufferHas(fail_marker));
    result();
}

/// Thread identity (docs/threading-plan.md M6): `thread-test` in id mode spawns two
/// workers that each read `runtime.Thread.getCurrentId`; the main thread confirms all
/// three ids are non-zero and distinct — proof each thread has its own kernel identity.
fn threadIdTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-id\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "id" })) true else |_| false;
        break;
    }
    check("thread-test (id mode) spawned", started);

    const ok_marker = "thread-id: ok";
    const fail_marker = "thread-id: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 12000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("each thread has a distinct, non-zero getCurrentId", bufferHas(ok_marker) and !bufferHas(fail_marker));
    result();
}

/// Thread-safe allocation (docs/threading-plan.md M7): `thread-test` in alloc mode runs N
/// threads that each do many `alloc`/fill/verify/`free` cycles of varied sizes on the
/// shared runtime heap. If the heap lock or the per-address-space mmap arena were unsafe,
/// two threads' blocks would overlap and a thread would read another's pattern; the
/// verdict marker is emitted only when every thread completes with every block intact.
fn threadAllocTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-alloc\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "alloc" })) true else |_| false;
        break;
    }
    check("thread-test (alloc mode) spawned", started);

    const ok_marker = "thread-alloc: ok";
    const fail_marker = "thread-alloc: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 20000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("concurrent heap allocation stayed corruption-free (shared heap + per-address-space arena)", bufferHas(ok_marker) and !bufferHas(fail_marker));
    result();
}

/// Per-thread TLS / FS base (docs/threading-plan.md M10): `thread-test` in tls mode has two
/// threads each set their own FS base and write a unique marker to `%fs:8`, then — after
/// both have written — read it back. If the FS base were not per-thread and restored across
/// context switches, the second write would clobber the first and a thread would read the
/// wrong marker. The verdict marker means both read their own value (no cross-talk).
fn threadTlsTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-tls\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "tls" })) true else |_| false;
        break;
    }
    check("thread-test (tls mode) spawned", started);

    const ok_marker = "thread-tls: ok";
    const fail_marker = "thread-tls: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 12000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("each thread has its own FS-base TLS slot (no cross-talk across switches)", bufferHas(ok_marker) and !bufferHas(fail_marker));
    result();
}

/// RwLock (docs/threading-plan.md M11): `thread-test` in rwlock mode runs writers that set
/// two halves of a value under the exclusive lock and readers that check the halves match
/// under the shared lock. If the reader/writer lock were wrong, a reader would observe a
/// half-written value; zero violations across many reads → the lock holds.
fn threadRwlockTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: thread-rwlock\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "thread-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "thread-test", "rwlock" })) true else |_| false;
        break;
    }
    check("thread-test (rwlock mode) spawned", started);

    const ok_marker = "thread-rwlock: ok";
    const fail_marker = "thread-rwlock: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 20000;
    while (architecture.millis() < deadline) {
        if (bufferHas(ok_marker) or bufferHas(fail_marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("readers/writers over an RwLock never observed a half-written value", bufferHas(ok_marker) and !bufferHas(fail_marker));
    result();
}

/// The task reaper (docs/threading-plan.md M8): a dead task's kernel stack used to be
/// leaked ("no reaper yet"). Spawn and kill many ring-3 processes and confirm the total
/// kernel-stack bytes return to baseline — every stack reclaimed, no leak. (Threads exit
/// through the same exitUserLocked path, so this covers them too.)
fn taskReapTest(boot_information: *const BootInformation) void {
    _ = boot_information;
    log("DANOS-TEST-BEGIN: task-reap\n", .{});
    const base = scheduler.liveStackBytes();
    const rounds: u32 = 12;
    var killed: u32 = 0;
    var round: u32 = 0;
    while (round < rounds) : (round += 1) {
        process.fault_kill_count = 0;
        const probe = spawnFaultingProcess() orelse break;
        _ = probe;
        scheduler.setPriority(1);
        const deadline = architecture.millis() + 5000;
        while (process.fault_kill_count < 1 and architecture.millis() < deadline) scheduler.yield();
        scheduler.setPriority(4);
        if (process.fault_kill_count >= 1) killed += 1;
    }
    // Reaping is asynchronous — a dead task's stack is freed when its core next switches
    // or ticks. Poll (bounded) until the live bytes return to baseline: a correct reaper
    // gets there in a few ms; a genuine leak never does and this times out.
    scheduler.setPriority(1);
    const settle_deadline = architecture.millis() + 3000;
    while (scheduler.liveStackBytes() != base and architecture.millis() < settle_deadline) scheduler.yield();
    scheduler.setPriority(4);

    const final = scheduler.liveStackBytes();
    log("task-reap: base={d} final={d} killed={d}/{d}\n", .{ base, final, killed, rounds });
    check("all probes spawned and were killed", killed == rounds);
    check("kernel stacks reclaimed to baseline (no leak)", final == base);
    if (killed == rounds and final == base)
        log("task-reap: kernel stacks reclaimed to baseline ok\n", .{});
    result();
}

/// The full PID-1 path: the bootloader read /system/services/init off the boot volume and
/// handed it over; load it as a user ELF and spawn it as a real ring-3 process
/// — the same call the normal boot path makes — then confirm it beats. init
/// heartbeats forever, so this proves it reaches ring 3, makes repeated syscalls
/// (write + sleep), and stays alive rather than exiting.
fn initTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: init\n", .{});
    const image = bundledInit(boot_information) orelse {
        check("initial_ramdisk carries /system/services/init", false);
        result();
        return;
    };
    check("initial_ramdisk carries /system/services/init", true);
    process.write_count = 0;
    const spawned = if (process.spawnProcess(image, 4, &.{"/system/services/init"})) true else |err| blk: {
        log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
        break :blk false;
    };
    check("init loaded and spawned as a process", spawned);

    // Wait (real time) for at least two heartbeats — proving it runs, writes,
    // and sleeps repeatedly (init sleeps ~1 s between beats).
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 8000;
    while (process.write_count < 2 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);

    const prefix = "init: heartbeat";
    const beat_ok = bufferHas(prefix);
    check("init produced repeated heartbeats (>=2)", process.write_count >= 2);
    check("heartbeat text arrived intact", beat_ok);
    check("heartbeats came from user mode (CPL 3)", process.write_from_user);
    check("init is still alive (did not exit)", process.exit_code == 0);
    result();
}

/// process_enumerate's kernel half: spawn two init processes next to the kernel
/// tasks and snapshot the table. The snapshot must list both by name with distinct,
/// kernel-supervised ids, include the kernel tasks (id 0, empty name), and report
/// the same total through a too-small buffer (the truncation contract: the caller
/// learns how big a buffer to bring).
fn processListTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: process-list\n", .{});
    const image = bundledInit(boot_information) orelse {
        check("initial_ramdisk carries /system/services/init", false);
        result();
        return;
    };
    check("initial_ramdisk carries /system/services/init", true);

    var spawned: u32 = 0;
    if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
    if (process.spawnProcess(image, 4, &.{"/system/services/init"})) spawned += 1 else |_| {}
    check("two init processes spawned", spawned == 2);

    var table: [32]abi.ProcessDescriptor = undefined;
    const total = scheduler.enumerate(&table);
    check("enumerate counts the boot task and both processes (>=3)", total >= 3);

    var inits: u32 = 0;
    var init_ids: [2]u32 = .{ 0, 0 };
    var kernel_task_seen = false;
    var states_sane = true;
    for (table[0..@min(total, table.len)]) |descriptor| {
        if (descriptor.state > @intFromEnum(abi.ProcessState.blocked)) states_sane = false;
        if (descriptor.name_length == 0) kernel_task_seen = true;
        if (eql(descriptor.name[0..descriptor.name_length], "/system/services/init")) {
            if (inits < 2) init_ids[inits] = descriptor.id;
            inits += 1;
            check("init entry is kernel-supervised (supervisor 0)", descriptor.supervisor == 0);
        }
    }
    check("both init processes listed by name", inits == 2);
    check("listed processes carry distinct ids", init_ids[0] != init_ids[1]);
    check("kernel tasks are listed too (empty name)", kernel_task_seen);
    check("every state is a ProcessState value", states_sane);

    var one: [1]abi.ProcessDescriptor = undefined;
    check("a too-small buffer still learns the true total", scheduler.enumerate(&one) == total);
    result();
}

/// process_kill + the exit notification, kernel half. Two victims, two paths:
/// - init, which heartbeats and sleeps: caught blocked, reaped on the killer's
///   own call — and its heartbeat must stop.
/// - process-test's spinner role (from the initial ramdisk), which loops in user
///   mode making no system calls: with more cores it is caught running, taking
///   the deferred path (kill_pending, finished by the victim core's next tick).
/// Each death must post one exit notification badge (exit bit + the child's id)
/// on the endpoint given at spawn; wrong-supervisor and unknown-id kills must be
/// refused. The waits block in replyWait, so a lost notification times the
/// harness out rather than passing vacuously.
fn processKillTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: process-kill\n", .{});
    const image = bundledInit(boot_information) orelse {
        check("initial_ramdisk carries /system/services/init", false);
        result();
        return;
    };
    check("initial_ramdisk carries /system/services/init", true);
    const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(ramdisk) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    const me = scheduler.currentId();
    const endpoint = ipcsync.createIpcEndpoint() orelse {
        check("exit endpoint allocated", false);
        result();
        return;
    };

    process.write_count = 0;
    const sleeper = process.spawnProcessSupervised(image, 4, &.{"/system/services/init"}, me, endpoint) catch 0;
    check("init spawned as the supervised sleeper victim", sleeper != 0);

    // Let it reach its heartbeat loop (write, then a 1 s sleep) so the kill most
    // likely catches it blocked.
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 8000;
    while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);
    check("victim heartbeat before the kill", process.write_count >= 1);

    // Kills that must be refused, before the one that must not be.
    check("a non-supervisor may not kill (-EPERM)", process.killProcess(me + 12345, sleeper) == -ipcsync.EPERM);
    check("an unknown id misses (-ESRCH)", process.killProcess(me, 0xFFFF_FF00) == -ipcsync.ESRCH);
    check("a kernel task is not a killable process (-ESRCH)", process.killProcess(me, 0) == -ipcsync.ESRCH);

    check("the supervisor's kill is accepted", process.killProcess(me, sleeper) == 0);

    var badge: u64 = 0;
    var received_cap: u64 = 0;
    var r = ipcsync.replyWait(endpoint, 0, 0, 0, 0, abi.no_cap, &badge, &received_cap);
    check("the sleeper's exit notification arrived (length 0)", r == 0);
    check("its badge carries the exit bit and the child id", badge == abi.notify_badge_bit | abi.notify_exit_bit | sleeper);

    // M17.2: the recorded reason — the notification is the fence, so it is
    // already readable, and gated by the same supervisor check as the kill.
    check("the sleeper's reason reads killed", process.exitReasonOf(me, sleeper) == @intFromEnum(abi.ExitReason.killed));
    check("a non-supervisor may not read the reason (-EPERM)", process.exitReasonOf(me + 12345, sleeper) == -ipcsync.EPERM);
    check("an unknown id has no reason (-ESRCH)", process.exitReasonOf(me, 0xFFFF_FF00) == -ipcsync.ESRCH);

    const beats_at_kill = process.write_count;
    scheduler.sleep(1500); // more than one heartbeat period
    check("the heartbeat stopped with the kill", process.write_count == beats_at_kill);

    // The spinner: no system calls, so only the tick can deliver a deferred kill.
    var spinner: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
        spinner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "spinner" }, me, endpoint) catch 0;
        break;
    }
    check("process-test spawned as the supervised spinner victim", spinner != 0);
    scheduler.sleep(100); // give another core a chance to be running it
    check("the spinner's kill is accepted", process.killProcess(me, spinner) == 0);
    r = ipcsync.replyWait(endpoint, 0, 0, 0, 0, abi.no_cap, &badge, &received_cap);
    check("the spinner's exit notification arrived (length 0)", r == 0);
    check("its badge carries the exit bit and the child id", badge == abi.notify_badge_bit | abi.notify_exit_bit | spinner);

    // M17.2: a child that ends on its own must read exited, not killed —
    // args-echo with arguments echoes once and returns from main.
    var clean: u32 = 0;
    i = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "args-echo")) continue;
        clean = process.spawnProcessSupervised(item.blob, 4, &.{ "args-echo", "clean-exit" }, me, endpoint) catch 0;
        break;
    }
    check("args-echo spawned as the clean-exit child", clean != 0);
    r = ipcsync.replyWait(endpoint, 0, 0, 0, 0, abi.no_cap, &badge, &received_cap);
    check("the clean child's exit notification arrived", badge == abi.notify_badge_bit | abi.notify_exit_bit | clean);
    check("the clean child's reason reads exited", process.exitReasonOf(me, clean) == @intFromEnum(abi.ExitReason.exited));

    var table: [32]abi.ProcessDescriptor = undefined;
    const total = scheduler.enumerate(&table);
    var still_listed = false;
    for (table[0..@min(total, table.len)]) |descriptor| {
        if (descriptor.id == sleeper or descriptor.id == spinner) still_listed = true;
    }
    check("neither victim is listed after its kill", !still_listed);
    check("a killed id stays dead (-ESRCH on a second kill)", process.killProcess(me, sleeper) == -ipcsync.ESRCH);
    result();
}

/// M17.1: a dead process's device claims are released by the reap, so a restarted
/// driver can claim its hardware again (docs/process-lifecycle.md iron rule 1).
/// First the broker release in isolation — two owners, one released, the other's
/// claim must survive. Then the death-path wiring with a real child: the claim is
/// made on the child's behalf (the broker is kernel-callable), the child is
/// killed, and once the exit notification arrives — posted last, after release —
/// the device must be unclaimed and claimable again.
fn claimReleaseTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: claim-release\n", .{});

    var buffer: [2]device_abi.DeviceDescriptor = undefined;
    const total = devices_broker.enumerate(&buffer);
    check("the device tree is seeded (>= 2 devices)", total >= 2);
    if (total < 2) {
        result();
        return;
    }

    // The broker release in isolation.
    check("device 0 claimed by owner 111", devices_broker.claim(0, 111));
    check("device 1 claimed by owner 222", devices_broker.claim(1, 222));
    devices_broker.releaseAllOwnedBy(111);
    check("owner 111's claim is released", devices_broker.ownerOf(0) == null);
    check("owner 222's claim survives", (devices_broker.ownerOf(1) orelse 0) == 222);
    devices_broker.releaseAllOwnedBy(222);
    check("cleanup released owner 222", devices_broker.ownerOf(1) == null);

    // The death-path wiring: a real process dies holding a claim.
    const image = bundledInit(boot_information) orelse {
        check("initial_ramdisk carries /system/services/init", false);
        result();
        return;
    };
    check("initial_ramdisk carries /system/services/init", true);
    const me = scheduler.currentId();
    const endpoint = ipcsync.createIpcEndpoint() orelse {
        check("exit endpoint allocated", false);
        result();
        return;
    };
    const child = process.spawnProcessSupervised(image, 4, &.{"/system/services/init"}, me, endpoint) catch 0;
    check("supervised child spawned", child != 0);
    check("device 0 claimed on the child's behalf", devices_broker.claim(0, child));

    check("the kill is accepted", process.killProcess(me, child) == 0);
    var badge: u64 = 0;
    var received_cap: u64 = 0;
    _ = ipcsync.replyWait(endpoint, 0, 0, 0, 0, abi.no_cap, &badge, &received_cap);
    check("the exit notification arrived", badge == abi.notify_badge_bit | abi.notify_exit_bit | child);
    check("death released the child's claim", devices_broker.ownerOf(0) == null);
    check("the device is claimable again", devices_broker.claim(0, me));
    devices_broker.releaseAllOwnedBy(me);
    result();
}

/// M17.3: the published exit events, proven by a stateful server. The fat
/// server subscribes at startup; a client opens a file on the volume and parks
/// holding the handle; the kill posts the exit event to fat's endpoint; fat
/// releases the dead client's handle and says so — the service-side mirror of
/// iron rule 1 (a service must never depend on clients cleaning up).
fn vfsClientDeathTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: vfs-client-death\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.write_count = 0;
    // The full tree: the storage chain must come up for /mnt/usb to exist —
    // the fat server (not a router) now owns client file state and its sweep.
    process.setInitialRamdisk(image);
    const init_ok = if (process.spawnBundled("/system/services/init")) true else |_| false;
    check("init spawned (boots the storage chain)", init_ok);

    const me = scheduler.currentId();
    const endpoint = ipcsync.createIpcEndpoint() orelse {
        check("exit endpoint allocated", false);
        result();
        return;
    };
    var client: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "vfs-test")) continue;
        client = process.spawnProcessSupervised(item.blob, 4, &.{ "vfs-test", "park" }, me, endpoint) catch 0;
        break;
    }
    check("parked client spawned (supervised)", client != 0);

    // Its heartbeat is the fence: once it beats, the handle is open. The park
    // waits out the whole USB->block->fat chain, so give it room; with the full
    // tree chattering, the ring (not the last-write buffer) is the evidence.
    const parked = "vfstest: parked";
    scheduler.setPriority(1);
    var deadline = architecture.millis() + 30000;
    while (architecture.millis() < deadline) {
        if (ringHas(parked)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);
    check("client parked holding an open handle", ringHas(parked));

    check("the kill is accepted", process.killProcess(me, client) == 0);
    var badge: u64 = 0;
    var received_cap: u64 = 0;
    _ = ipcsync.replyWait(endpoint, 0, 0, 0, 0, abi.no_cap, &badge, &received_cap);
    check("the exit notification arrived", badge == abi.notify_badge_bit | abi.notify_exit_bit | client);

    // The fat server heard the same published event; its release line in the
    // ring is the proof.
    const released = "released 1 handle(s) for dead client";
    scheduler.setPriority(1);
    deadline = architecture.millis() + 10000;
    while (architecture.millis() < deadline) {
        if (ringHas(released)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);
    check("the fat server released the dead client's handle", ringHas(released));
    result();
}

/// M17.4 from ring 3: process-test's signal-run role drives the whole lifecycle
/// surface — the zero-length ping (answered by the harness), signals as
/// statements (reload logged, terminate = clean exit), the one-shot timer, and
/// both endings of the stop sequence (polite -> exited, deaf -> killed at the
/// deadline). Its "process-test: signals ok" is the pass marker.
fn signalsTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: signals\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.setInitialRamdisk(image); // the parent system_spawns its children by name
    process.write_count = 0;
    var runner: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
        runner = process.spawnProcessSupervised(item.blob, 4, &.{ "process-test", "signal-run" }, scheduler.currentId(), null) catch 0;
        break;
    }
    check("signal-run parent spawned", runner != 0);

    const pass_marker = "process-test: signals ok";
    const fail_marker = "process-test: FAIL";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 15000;
    var saw_pass = false;
    var saw_fail = false;
    while (architecture.millis() < deadline and !saw_pass and !saw_fail) {
        if (bufferHas(pass_marker)) saw_pass = true;
        if (bufferHas(fail_marker)) saw_fail = true;
        scheduler.yield();
    }
    scheduler.setPriority(4);
    check("the signal-run parent reported ok", saw_pass and !saw_fail);
    result();
}

/// M18.1: the device manager's restart machinery, end to end. In test-restart
/// mode the manager also supervises crash-test: a fixture that claims device 0,
/// hellos, and faults. The scenario asserts three markers in order — the real
/// xHCI driver hellos clean and stays; crash-test is restarted with backoff
/// (each respawn re-claiming the device the dead instance held, M17.1 through
/// the manager's path); the crash loop caps and the manager gives up.
fn driverRestartTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: driver-restart\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.setInitialRamdisk(image); // the manager system_spawns drivers by name
    process.write_count = 0;
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-restart" }, scheduler.currentId(), null) catch 0;
        break;
    }
    check("device-manager spawned in test-restart mode", manager != 0);
    // The assertions live in the harness: its expect regex requires, in order,
    // the xHCI hello ack, a crash-test restart, and the crash-loop cap — read
    // from the whole serial capture, immune to the transient-line races a
    // write_buffer poll would have here (many processes log concurrently).
    result();
}

/// M18.2: bus tree reports, end to end. The manager (test-usb-restart mode)
/// spawns the xHCI driver; the driver maps its BAR, scans the root-hub ports,
/// and reports the two QEMU devices; the manager mirrors them, kills the
/// reporter (the test trigger), prunes both children, restarts the driver with
/// backoff, and the respawned instance re-claims, re-scans, and re-reports.
/// The harness's ordered expect regex is the assertion; this test only
/// orchestrates the spawn.
fn usbReportTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: usb-report\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.setInitialRamdisk(image);
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
        break;
    }
    check("device-manager spawned in test-usb-restart mode", manager != 0);
    result();
}

/// M18.3: the application surface. device-list enumerates the manager's tree
/// over IPC, subscribes with its endpoint as a capability, and prints every
/// published event; the manager's delayed test-kill of the reporter produces a
/// removed/added storm the subscriber must observe. The harness's ordered
/// expect regex is the assertion.
fn deviceListTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: device-list\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.setInitialRamdisk(image);
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-usb-restart" }, scheduler.currentId(), null) catch 0;
        break;
    }
    check("device-manager spawned in test-usb-restart mode", manager != 0);
    check("device-list spawned", spawnNamed(rd, "device-list"));
    result();
}

/// M19.1: the ring-3 PCI scan agrees with the kernel's. The manager spawns
/// pci-bus for the host bridge; the driver walks the same ECAM window through
/// its mmio_map grant and must find exactly the functions the kernel's own
/// enumeration recorded — the equivalence that licenses retiring the kernel
/// walk in M19.3.
fn pciScanTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: pci-scan\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    // Post-flip (M19.3) ground truth: the kernel no longer enumerates PCI
    // functions, so equivalence inverts — every PCI function the broker holds was
    // put there by the ring-3 driver, so before the driver runs the broker holds
    // none. One reusable descriptor buffer (each snapshot is ~20 KiB; three live
    // at once would overflow the 64 KiB bootstrap stack this test runs on).
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    check("the kernel seeded no PCI functions (the walk retired)", brokerPciCount(&buffer) == 0);

    process.setInitialRamdisk(image);
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-pci-restart" }, scheduler.currentId(), null) catch 0;
        break;
    }
    check("device-manager spawned (test-pci-restart mode)", manager != 0);

    // The manager spawns pci-bus, which scans the ECAM window and registers every
    // function it finds, so the broker's PCI count climbs from zero and plateaus.
    // Wait for it to *settle*: latch N only once the count has held steady for a
    // stretch, so a mid-scan sample can't latch a low N that the rest of the same
    // scan then appears to exceed. The count is monotonic (registrations only add;
    // the table has no unregister) so the plateau is permanent — stability is
    // reached the moment the scan finishes and holds indefinitely. Sleep between
    // samples rather than busy-yield: the boot context outranks the drivers, and a
    // busy spin would starve the very processes it waits on; a sleeping task is
    // woken by the timer, so the drivers run in between.
    const poll_ms = 5;
    var registered: u32 = 0;
    var steady: u32 = 0;
    var deadline = architecture.millis() + 15000;
    while (architecture.millis() < deadline and steady < 60) { // 60 * 5ms = 300ms steady
        const now = brokerPciCount(&buffer);
        if (now != 0 and now == registered) steady += 1 else steady = 0;
        registered = now;
        scheduler.sleep(poll_ms);
    }
    check("the ring-3 scan registered its PCI functions in the broker", registered >= 1);

    // The restart drill — the manager kills pci-bus ~1 s after its scan, prunes its
    // own child tree, and respawns it to re-claim, re-scan, and re-register — is
    // asserted by the harness's ordered regex over the whole serial log, the way
    // every restart drill is (see usbReportTest / driverRestartTest): the manager's
    // kill/prune/respawn lines are transient and would race a write_buffer poll, and
    // the *broker* count can't witness the restart at all — the table has no
    // unregister and register is idempotent (devices-broker.zig), so the kill leaves
    // the nodes in place and the respawn's re-registration dedupes against them.
    //
    // That idempotence is exactly this test's kernel-side claim: watch, across the
    // whole drill, that the count never grows past N. A broken dedup would append
    // the re-scanned functions as duplicates (N -> 2N), and with no unregister that
    // overshoot would persist — so a single late sample would catch it; the loop is
    // belt-and-braces over the ~2 s the kill + backoff + respawn takes.
    var duplicated = false;
    deadline = architecture.millis() + 5000;
    while (architecture.millis() < deadline and !duplicated) {
        if (brokerPciCount(&buffer) > registered) duplicated = true;
        scheduler.sleep(20);
    }
    check("no duplicate PCI nodes after the restart drill", !duplicated);
    result();
}

/// M21.3 capstone: orderly shutdown. Boot init with the initial-ramdisk
/// published, so init spawns the full service tree (vfs, input, device-manager
/// -> discovery/acpi); the harness injects a real power-button event via QMP;
/// the acpi service publishes it; init runs the stop sequence over its children
/// and asks the power service for S5; the machine powers off (QEMU exits). The
/// kernel test only spawns init — the ordered chain is the harness assertion.
/// The USB HID chain, end to end: boot the full service tree (init spawns vfs,
/// input, device-manager), and let discovery run — the manager matches the PCI
/// host bridge to pci-bus, pci-bus reports the xHCI controller, usb-xhci-bus
/// enumerates the HID interfaces, and the manager spawns the class drivers. The
/// harness's expect regex requires usb-xhci-bus to register the boot-keyboard
/// interface, the manager to spawn usb-hid-keyboard, and that driver to come up
/// (open its device, ask for boot protocol, subscribe) — proof the transfer
/// protocol works class-driver to controller.
fn usbHidTest(boot_information: *const BootInformation) void {
    bootServiceTreeTest(boot_information, "usb-hid");
}

/// The USB storage chain: same full-tree boot, but the harness attaches a
/// usb-storage device and the expect regex requires usb-storage to come up
/// (open its device, run the BOT bring-up, read its capacity, and read block 0).
fn usbStorageTest(boot_information: *const BootInformation) void {
    bootServiceTreeTest(boot_information, "usb-storage");
}

/// The FAT mount chain: boot the full tree (init spawns the fat server, which
/// brings up the USB storage chain, mounts the FAT volume, and mounts itself into
/// the VFS at /mnt/usb), then spawn a fat-test client that lists and reads through
/// the mount. The harness attaches a usb-storage device; the expect regex requires
/// the fat mount and the client's success.
fn fatMountTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: fat-mount\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over the initial_ramdisk", false);
        result();
        return;
    }
    const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(ramdisk) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };
    process.setInitialRamdisk(ramdisk);
    const init_ok = if (process.spawnBundled("/system/services/init")) true else |_| false;
    check("init spawned (boots the tree, incl. the fat server)", init_ok);
    check("fat-test client spawned", spawnNamed(rd, "fat-test"));
    result();
}

fn bootServiceTreeTest(boot_information: *const BootInformation, comptime label: []const u8) void {
    log("DANOS-TEST-BEGIN: " ++ label ++ "\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over the initial_ramdisk", false);
        result();
        return;
    }
    const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    process.setInitialRamdisk(ramdisk);
    const spawned = if (process.spawnBundled("/system/services/init")) true else |_| false;
    check("init spawned (boots vfs, input, device-manager, and the USB chain)", spawned);
    result();
}

fn orderlyShutdownTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: orderly-shutdown\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over the initial_ramdisk", false);
        result();
        return;
    }
    const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    process.setInitialRamdisk(ramdisk);
    const spawned = if (process.spawnBundled("/system/services/init")) true else |_| false;
    check("init spawned as PID root of user space", spawned);
    result();
}

/// M20.2: the acpi service registers + reports its _HID devices. Boot normally
/// (the manager spawns discovery); the harness's expect regex requires the two
/// PS/2 nodes among the service's report lines, each with its _CRS resources —
/// the ring-3 _CRS/_STA evaluation working end to end. The kernel test only
/// starts the manager.
fn acpiReportTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: acpi-report\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };
    process.setInitialRamdisk(image);
    var spawned = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        _ = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
        spawned = true;
        break;
    }
    check("device-manager spawned", spawned);
    result();
}

/// M20.1: the ring-3 AML parse works. The manager spawns the discovery service
/// (the acpi build variant); it claims the acpi-tables node, maps the blobs,
/// parses them, and self-verifies it found at least a floor of Device objects,
/// printing "acpi-parse: ok". The kernel no longer parses AML, so there is no
/// kernel count to compare against — the ring-3 parse is now the only one.
fn acpiParseTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: acpi-parse\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    // Spawn the discovery service directly with a device-count *floor* as argv:
    // it parses the blobs in ring 3 and self-verifies it found at least that many
    // Device objects, printing "acpi-parse: ok". A floor of 1 just proves the
    // parser ran and produced a namespace (the QEMU q35 DSDT has dozens). The
    // marker is deterministic — no racing the shared serial buffer.
    process.setInitialRamdisk(image);
    var spawned = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "discovery")) continue;
        _ = process.spawnProcessSupervised(item.blob, 4, &.{ "discovery", "1" }, scheduler.currentId(), null) catch 0;
        spawned = true;
        break;
    }
    check("discovery service spawned", spawned);
    result();
}

/// The whole user-side surface at once: spawn process-test's supervisor role,
/// which — entirely from ring 3 — creates an exit endpoint, spawns its two
/// children supervised, sees them in process_enumerate, kills them (one blocked,
/// one spinning), collects both exit notifications, and confirms they are gone.
/// Its "process-test: ok" is the pass marker; any FAIL line is specific.
fn supervisionTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: supervision\n", .{});
    check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
    if (boot_information.initial_ramdisk_len == 0) {
        result();
        return;
    }
    const ramdisk = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(ramdisk) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };
    process.setInitialRamdisk(ramdisk); // the supervisor system_spawns its children by name

    process.write_count = 0;
    process.write_from_user = false;
    var started = false;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "process-test")) continue;
        started = if (process.spawnProcess(item.blob, 4, &.{ "process-test", "run" })) true else |_| false;
        break;
    }
    check("process-test spawned as the user-space supervisor", started);

    const marker = "process-test: ok";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 10000;
    while (architecture.millis() < deadline) {
        if (bufferHas(marker)) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    const ok = bufferHas(marker);
    if (!ok and process.write_len > 0) log("DANOS-SUPERVISION: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
    check("the supervisor completed every step (spawn/list/kill/notify)", ok);
    check("it ran in user mode (CPL 3)", process.write_from_user);
    result();
}

/// The initial_ramdisk path: the bootloader handed over an image bundling extra user
/// binaries; parse it, spawn every program, and confirm one (the vfs stub)
/// reaches ring 3 and heartbeats — proving the whole ferry-parse-spawn pipeline.
fn initialRamdiskTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: initial_ramdisk\n", .{});
    check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
    if (boot_information.initial_ramdisk_len == 0) {
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };
    check("initial_ramdisk image is valid", true);
    check("initial_ramdisk contains at least one binary", rd.count >= 1);

    process.write_count = 0;
    process.write_from_user = false;
    var spawned: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (process.spawnProcess(item.blob, 4, &.{item.name})) spawned += 1 else |err| {
            log("DANOS-INITRD-ERR: {s}: {s}\n", .{ item.name, @errorName(err) });
        }
    }
    check("every initial_ramdisk binary spawned", spawned == rd.count);

    // Wait for the spawned programs to run and make syscalls (they write + sleep).
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 8000;
    while (process.write_count < 2 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);

    check("initial_ramdisk processes ran and made syscalls (>=2)", process.write_count >= 2);
    check("syscalls came from user mode (CPL 3)", process.write_from_user);
    result();
}

/// The full VFS path: spawn the user-space VFS server and a client from the
/// initial_ramdisk. The client opens a file through the runtime file API, writes, seeks, reads
/// it back, and — only if the round trip matched — heartbeats "vfstest: ok". So
/// seeing that marker proves client open/write/read reached the server over IPC
/// and came back correct. (The client retries until the server registers.)
fn vfsTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: vfs\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.write_count = 0;
    process.write_from_user = false;
    // Seed the kernel VFS (/system) — the router the client exercises.
    process.setInitialRamdisk(image);
    // Spawn just the client: the kernel itself is the VFS root it exercises
    // (resolve + fs_node over /system through the plain runtime.fs API).
    _ = spawnNamed(rd, "vfs-test");

    // Wait for the client's success heartbeat (it round-trips, then beats ~1/s).
    const prefix = "vfstest: ok";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 10000;
    while (architecture.millis() < deadline) {
        if (bufferHas(prefix) and process.write_count >= 2) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    const ok = bufferHas(prefix);
    check("client completed the VFS round trip (open/write/read matched)", ok);
    check("the round trip ran repeatedly (server stays up)", process.write_count >= 2);
    check("client syscalls came from user mode (CPL 3)", process.write_from_user);
    result();
}

/// The full input path: spawn the input service, a synthetic source, and a subscriber from
/// the initial_ramdisk. The source publishes keyboard, mouse, and joystick events in turn;
/// the service routes them (with the asynchronous ipc_send) to the subscriber, which took
/// all three classes and — only once it has received one — heartbeats "input-test: ok".
/// Seeing that marker proves an event travelled source -> service -> subscriber over IPC,
/// exercising the async buffered-send primitive, capability-passing subscription, and
/// per-device routing. The source and service stay silent after startup so the subscriber's
/// line is the one left in the shared evidence buffer.
fn inputTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: input\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.write_count = 0;
    process.write_from_user = false;
    _ = spawnNamed(rd, "input"); // the fan-out service
    _ = spawnNamed(rd, "input-source"); // a synthetic keyboard publishing events
    _ = spawnNamed(rd, "input-test"); // the subscriber whose "ok" line is the marker

    // Wait for the subscriber's success heartbeat (it beats once per received event).
    const prefix = "input-test: ok";
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 12000;
    while (architecture.millis() < deadline) {
        if (bufferHas(prefix) and process.write_count >= 2) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    const ok = bufferHas(prefix);
    check("a subscriber received a broadcast key event over IPC (source -> service -> subscriber)", ok);
    check("events kept flowing (service + async send stay up)", process.write_count >= 2);
    check("client syscalls came from user mode (CPL 3)", process.write_from_user);
    result();
}

/// D2 — the display service comes up. Spawn it from the initial_ramdisk; it claims the
/// framebuffer the kernel seeded (D1), maps it write-combining, allocates a cacheable
/// back buffer, and proves the double-buffer path by clearing that buffer and presenting
/// it. Its `display: online WxH` + `display: presented frame 0` heartbeats are the
/// markers — seeing them proves a user-space compositor took the framebuffer and pushed
/// a whole composed frame to the screen, without ever drawing straight to the LFB.
fn displayServiceTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: display-service\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    // Spawn the compositor and hand it the core. Its own serial heartbeats — `display:
    // online WxH` and `display: presented frame 0` — are what the harness matches (it
    // reads serial directly, like the fault cases). We don't poll for them in-kernel: a
    // single service that comes up and blocks doesn't reschedule this bring-up context
    // (there is no other runnable task to bounce control back through), so the honest
    // observation point is the service's output itself, not a check() proxy here.
    if (!spawnNamed(rd, "display")) {
        log("display-service: could not spawn the display service\n", .{});
        result();
        return;
    }
    scheduler.setPriority(1); // below the service, so it runs and comes up first
    while (true) scheduler.yield();
}

/// The threaded compositor tracks a mouse (docs/threading.md, docs/display.md). Spawn the
/// `input` fan-out service, the display (which runs a mouse-listener thread alongside its
/// compositor loop and draws a top-z cursor), and `input-source` in `mouse` mode — a
/// synthetic source publishing pure motion. The display's own marker,
/// `display: cursor tracking mouse ok`, is printed once the cursor has tracked a run of
/// motion end to end (source -> input service -> listener thread -> channel -> render), so
/// like the other display cases we match on serial rather than poll in-kernel.
fn displayCursorTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: display-cursor\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    if (!spawnNamed(rd, "input")) {
        log("display-cursor: could not spawn the input service\n", .{});
        result();
        return;
    }
    if (!spawnNamed(rd, "display")) {
        log("display-cursor: could not spawn the display service\n", .{});
        result();
        return;
    }
    if (!spawnNamedWithArg(rd, "input-source", "mouse")) {
        log("display-cursor: could not spawn the mouse source\n", .{});
        result();
        return;
    }
    scheduler.setPriority(1); // below the services, so they run
    while (true) scheduler.yield();
}

/// D4 — a separate process drives the compositor. Spawn the display service and the
/// hardware-free `display-demo` client, which creates a wallpaper, a moving rectangle,
/// and a cursor and presents a run of frames. Its `display-demo: ok` heartbeat — printed
/// only after it drove frames of motion through the layer client API and the compositor —
/// is the harness's marker (the visible motion itself is a screenshot away via run-x86-64).
/// The demo keeps presenting, so unlike a lone blocking service the scheduler stays busy;
/// we still match on serial rather than poll, for consistency.
fn displayDemoTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: display-demo\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    if (!spawnNamed(rd, "display")) {
        log("display-demo: could not spawn the display service\n", .{});
        result();
        return;
    }
    // Spawn the input service too — real boot has it, and it guards the demo's
    // independence from input: the demo must animate to `display-demo: ok` on its own
    // frame timer even with the input service available (a client that blocks its
    // animation loop on a mouse read would stall here, never reaching the marker).
    _ = spawnNamed(rd, "input");
    _ = spawnNamed(rd, "display-demo");
    scheduler.setPriority(1); // below the service + demo, so they run
    while (true) scheduler.yield();
}

/// V2 — cross-process shared memory (docs/display-v2.md). Spawn shared-memory-server and shared-memory-client:
/// the client shared_memory_creates a region, writes a pattern, and passes the region's capability to
/// the server as an ipc_call send_cap; the server shared_memory_maps it and confirms the pattern is
/// visible — proving the two processes share the same physical pages, and that the extended
/// capability-passing (endpoints → memory objects) works. Its `shared-memory: shared 4096 bytes ok`
/// heartbeat is the marker.
fn sharedMemoryTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: shared-memory\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    if (!spawnNamed(rd, "shared-memory-server")) {
        log("shared-memory: could not spawn shared-memory-server\n", .{});
        result();
        return;
    }
    _ = spawnNamed(rd, "shared-memory-client");
    scheduler.setPriority(1); // below the two, so they run
    while (true) scheduler.yield();
}

/// V3 — the virtio-gpu driver, end to end (docs/display-v2.md). Boot the device-manager
/// stack (in its normal mode) so it discovers the virtio-gpu PCI function — present because
/// the harness boots this case with QEMU's `-device virtio-gpu-pci` — and spawns the driver.
/// The driver claims the device, brings up the control virtqueue, creates a 2D scanout,
/// paints a known pattern, flushes it, and waits for the device's used-ring ack. Its serial
/// heartbeats — `virtio-gpu: scanout WxH online` and `virtio-gpu: flush acked, pixel check
/// ok` — are the harness's markers (it reads serial directly, like the display cases). The
/// used-ring ack is the device confirming it consumed the frame; the pixel read-back proves
/// the backing is CPU-visible RAM — together the automated stand-in for "it's on screen".
fn virtioGpuTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: virtio-gpu\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    // Spawn device-manager in its normal mode: its initialise discovers the PCI host bridge
    // from the kernel device tree, spawns pci-bus, and matches the virtio-gpu class triple to
    // spawn our driver with the function's device id as argv[1].
    process.setInitialRamdisk(image);
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
        break;
    }
    if (manager == 0) {
        log("virtio-gpu: could not spawn device-manager\n", .{});
        result();
        return;
    }
    scheduler.setPriority(1); // below the manager and the driver it spawns, so they run
    while (true) scheduler.yield();
}

/// V4 — the native backend + hot-attach (docs/display-v2.md). Boot the compositor and the
/// hardware-free `display-demo` client (as displayDemoTest does), then the device-manager
/// stack so it discovers the virtio-gpu function — present via QEMU's `-device
/// virtio-gpu-pci` — and spawns the driver. The driver brings up its scanout, then announces
/// the shared surface to the already-running compositor, which maps it, upgrades off the GOP
/// floor, and presents the composited frame through the native backend. Its serial heartbeats
/// — `display: scanout upgraded to virtio-gpu` and `display: native present verified` — plus
/// the demo's own `display-demo: ok` are the harness's markers. Display is spawned first so
/// it is registered on `.display` before the driver announces.
fn displayNativeTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: display-native\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.setInitialRamdisk(image);
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{"device-manager"}, scheduler.currentId(), null) catch 0;
        break;
    }
    if (manager == 0) {
        log("display-native: could not spawn device-manager\n", .{});
        result();
        return;
    }
    if (!spawnNamed(rd, "display")) {
        log("display-native: could not spawn the display service\n", .{});
        result();
        return;
    }
    _ = spawnNamed(rd, "display-demo");
    scheduler.setPriority(1); // below the compositor, the demo, and the driver, so they run
    while (true) scheduler.yield();
}

/// V6 — resilience: the compositor survives the virtio-gpu driver dying and re-attaches when
/// device-manager restarts it (docs/display-v2.md). Same boot as display-native, but the
/// manager runs in "test-scanout-restart" mode: a moment after the driver hellos, it kills it
/// once; the normal restart policy respawns it, the restarted driver re-announces, and the
/// compositor re-attaches to the fresh scanout — logging `display: scanout re-attached` after
/// the initial `display: scanout upgraded to virtio-gpu`. The compositor must not crash.
fn displayReattachTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: display-reattach\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    process.setInitialRamdisk(image);
    var manager: u32 = 0;
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (!eql(initial_ramdisk.basename(item.name), "device-manager")) continue;
        manager = process.spawnProcessSupervised(item.blob, 4, &.{ "device-manager", "test-scanout-restart" }, scheduler.currentId(), null) catch 0;
        break;
    }
    if (manager == 0) {
        log("display-reattach: could not spawn device-manager\n", .{});
        result();
        return;
    }
    if (!spawnNamed(rd, "display")) {
        log("display-reattach: could not spawn the display service\n", .{});
        result();
        return;
    }
    _ = spawnNamed(rd, "display-demo");
    scheduler.setPriority(1); // below the compositor, the demo, and the driver, so they run
    while (true) scheduler.yield();
}

/// Process arguments, end to end: spawn args-echo bare (its argv[0] is the
/// initial-ramdisk name). Instance 1 sees argc == 1 and respawns itself through
/// `system_spawn` with the extra arguments "alpha beta-42" — the syscall argument
/// blob. Instance 2 parses the kernel-built System V entry stack via the runtime
/// and echoes its whole argv in one write, which must arrive exactly as sent.
fn argsTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: args\n", .{});
    check("bootloader handed over an initial_ramdisk", boot_information.initial_ramdisk_len != 0);
    if (boot_information.initial_ramdisk_len == 0) {
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };
    process.setInitialRamdisk(image); // args-echo respawns itself through system_spawn

    process.write_count = 0;
    process.write_from_user = false;
    check("args-echo spawned from the initial_ramdisk", spawnNamed(rd, "args-echo"));

    // Wait for the *second* instance's echo (the first writes nothing).
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 8000;
    while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
    scheduler.setPriority(4);

    const expected = "args: /system/tests/args-echo alpha beta-42\n";
    const echoed = process.write_len == expected.len and eql(process.write_buffer[0..process.write_len], expected);
    if (!echoed and process.write_len > 0) log("DANOS-ARGS: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
    check("argv arrived intact (argv[0] = name, argv[1..] = spawn arguments)", echoed);
    check("echo came from user mode (CPL 3)", process.write_from_user);
    result();
}

/// Spawn the initial_ramdisk binary named `name` as a ring-3 process. Returns false if it
/// isn't in the image or fails to load.
/// The init ELF image out of the initial_ramdisk — init rides the table like
/// every other binary since the loader packs the whole /system tree.
fn bundledInit(boot_information: *const BootInformation) ?[]const u8 {
    if (boot_information.initial_ramdisk_len == 0) return null;
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse return null;
    const item = rd.find("/system/services/init") orelse return null;
    return item.blob;
}

/// The kernel VFS root (M-F): resolve initrd paths to node tokens, read an ELF
/// header through nodeRead, enumerate /system's derived directory table, and
/// verify the read-only + unknown-path refusals. Pure kernel-side — the
/// syscall surface gets its end-to-end coverage when runtime.fs cuts over.
fn kernelVfsTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: kvfs\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over the initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    process.setInitialRamdisk(image); // also seeds the kernel VFS /system mount

    // A file resolves to a kernel node token; its status and bytes are served.
    const resolved = kernel_vfs.resolvePath("/system/services/init", false);
    const is_file = resolved == .kernel_node;
    check("/system/services/init resolves to a kernel node", is_file);
    if (is_file) {
        const status = kernel_vfs.nodeStatus(resolved.kernel_node);
        check("its status is a non-empty regular file", status != null and status.?.kind == abi.file_kind_regular and status.?.size > 0);
        var header: [4]u8 = undefined;
        const n = kernel_vfs.nodeRead(resolved.kernel_node, 0, &header) orelse 0;
        check("its first bytes are an ELF magic", n == 4 and header[0] == 0x7f and header[1] == 'E' and header[2] == 'L' and header[3] == 'F');
    }

    // Directories resolve and enumerate: /system lists services/drivers/tests.
    const root_directory = kernel_vfs.resolvePath("/system", false);
    check("/system resolves to a directory node", root_directory == .kernel_node);
    var saw_services = false;
    var saw_drivers = false;
    var saw_files_in_services = false;
    if (root_directory == .kernel_node) {
        var cursor: u64 = 0;
        var name: [64]u8 = undefined;
        while (kernel_vfs.nodeReaddir(root_directory.kernel_node, cursor, &name)) |entry| : (cursor += 1) {
            if (eql(name[0..entry.name_len], "services")) saw_services = true;
            if (eql(name[0..entry.name_len], "drivers")) saw_drivers = true;
        }
    }
    check("readdir /system yields services and drivers", saw_services and saw_drivers);
    const services = kernel_vfs.resolvePath("/system/services", false);
    if (services == .kernel_node) {
        var cursor: u64 = 0;
        var name: [64]u8 = undefined;
        while (kernel_vfs.nodeReaddir(services.kernel_node, cursor, &name)) |entry| : (cursor += 1) {
            if (eql(name[0..entry.name_len], "init")) saw_files_in_services = true;
        }
    }
    check("readdir /system/services yields init", saw_files_in_services);

    // Refusals: unknown paths, and create-intent on the immutable initrd.
    check("an unknown path does not resolve", kernel_vfs.resolvePath("/system/services/no-such", false) == .not_found);
    check("an unmounted absolute path does not resolve", kernel_vfs.resolvePath("/elsewhere", false) == .not_found);
    check("create on /system is refused (read-only)", kernel_vfs.resolvePath("/system/services/new-file", true) == .not_found);
    result();
}

fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (eql(initial_ramdisk.basename(item.name), name)) {
            return if (process.spawnProcess(item.blob, 4, &.{item.name})) true else |_| false;
        }
    }
    return false;
}

/// As `spawnNamed`, but passes one extra argv entry (argv[1]) — e.g. a mode selector like
/// `input-source mouse`.
fn spawnNamedWithArg(rd: initial_ramdisk.Reader, name: []const u8, arg: []const u8) bool {
    var i: u32 = 0;
    while (i < rd.count) : (i += 1) {
        const item = rd.entry(i) orelse continue;
        if (eql(initial_ramdisk.basename(item.name), name)) {
            return if (process.spawnProcess(item.blob, 4, &.{ item.name, arg })) true else |_| false;
        }
    }
    return false;
}

/// The GSI discovery recorded for the HPET, from the same device table drivers see.
fn hpetGsi() ?u32 {
    var buffer: [16]device_abi.DeviceDescriptor = undefined;
    const n = @min(devices_broker.enumerate(&buffer), buffer.len);
    for (buffer[0..n]) |d| {
        if (d.class != @intFromEnum(device_abi.DeviceClass.timer)) continue;
        if (d.parent != device_abi.no_parent) continue; // the block, not a comparator child
        for (0..d.resource_count) |j| {
            const r = d.resources[j];
            if (r.kind == @intFromEnum(device_abi.ResourceKind.irq)) return @intCast(r.start);
        }
    }
    return null;
}

/// `device_register` containment — a kernel security property, tested directly against
/// the broker (no user-space demo driver). A bus driver publishes children of a device
/// it owns; the kernel must **refuse** any child whose resource escapes the parent's
/// grant, or `device_register` would become a system call for mapping arbitrary physical
/// memory. This is the property the old `bus` demo driver proved end-to-end; with the
/// demo gone, the property is asserted where it lives — in the kernel. Also checks the
/// idempotence rule (M19.0): re-registering an identical child returns the same id
/// instead of appending a duplicate.
fn containmentTest() void {
    log("DANOS-TEST-BEGIN: containment\n", .{});

    const me = scheduler.currentId();
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    check("device tree is seeded", devices_broker.enumerate(&buffer) >= 1);

    // The kernel-seeded HPET timer block is a device with a memory resource — a natural
    // parent to publish sub-window children under, as a PCI bridge or USB hub would.
    const parent_id = hpetDeviceId() orelse {
        check("found a device with a memory window to parent children under", false);
        result();
        return;
    };
    const parent = buffer[@intCast(parent_id)];
    var window: ?device_abi.ResourceDescriptor = null;
    for (0..parent.resource_count) |j| {
        if (parent.resources[j].kind == @intFromEnum(device_abi.ResourceKind.memory)) window = parent.resources[j];
    }
    const parent_window = window orelse {
        check("parent exposes a memory window", false);
        result();
        return;
    };

    if (devices_broker.ownerOf(parent_id) != null) {
        check("parent device was unclaimed at the start of the test", false);
        result();
        return;
    }
    check("claimed the parent device", devices_broker.claim(parent_id, me));
    defer devices_broker.releaseAllOwnedBy(me);

    // A child whose window lies inside the parent's is accepted.
    var fits = childDescriptor("cfit", parent_window.start, 0x20);
    const before = devices_broker.enumerate(&buffer);
    const good = devices_broker.register(parent_id, me, &fits) catch 0;
    check("a contained child is registered", good != 0);
    check("the contained child was appended to the table", devices_broker.enumerate(&buffer) == before + 1);

    // A child whose window escapes the parent's is refused with NotContained.
    var escapes = childDescriptor("cesc", parent_window.start, parent_window.len + 0x1000);
    const refused = if (devices_broker.register(parent_id, me, &escapes)) |_| false else |err| err == error.NotContained;
    check("an out-of-window child is refused (NotContained)", refused);
    check("the refused child left the table unchanged", devices_broker.enumerate(&buffer) == before + 1);

    // Idempotent on exact match: re-registering the accepted child returns its id and
    // appends nothing (M19.0 — a restarted bus re-reports what it rediscovers).
    const again = devices_broker.register(parent_id, me, &fits) catch 0;
    check("re-registering an identical child returns the same id", again != 0 and again == good);
    check("re-registering grew nothing", devices_broker.enumerate(&buffer) == before + 1);

    result();
}

/// A minimal child descriptor with one memory resource, for the containment test.
fn childDescriptor(hid: []const u8, start: u64, len: u64) device_abi.DeviceDescriptor {
    var child = std.mem.zeroes(device_abi.DeviceDescriptor);
    child.class = @intFromEnum(device_abi.DeviceClass.unknown);
    child.pci_class = device_abi.no_pci_class;
    child.hid_len = @intCast(hid.len);
    @memcpy(child.hid[0..hid.len], hid);
    child.resource_count = 1;
    child.resources[0] = .{ .kind = @intFromEnum(device_abi.ResourceKind.memory), .start = start, .len = len };
    return child;
}

/// The device manager (a ring-3 service) enumerates /system/devices, matches each
/// device to a driver, and spawns it. Proof of the whole discover -> match -> spawn ->
/// driver-up chain: boot only the device-manager; it must discover the PCI host bridge,
/// match `pci-bus`, and spawn it (with the bridge id as its argument) — and the spawned
/// pci-bus must reach its own live marker. It uses no special privilege — the same
/// `device_enumerate` any process could call.
fn deviceManagerTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: device-manager\n", .{});
    if (boot_information.initial_ramdisk_len == 0) {
        check("bootloader handed over an initial_ramdisk", false);
        result();
        return;
    }
    const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
    const rd = initial_ramdisk.Reader.init(image) orelse {
        check("initial_ramdisk image is valid", false);
        result();
        return;
    };

    // Let `system_spawn` find bundled binaries by name (the normal boot path does
    // this too). Only the device-manager is spawned here — so if `pci-bus` runs at
    // all, it's because the manager discovered the PCI host bridge, matched, and
    // spawned it.
    process.setInitialRamdisk(image);

    process.write_count = 0;
    process.write_from_user = false;
    check("device-manager spawned from the initial_ramdisk", spawnNamed(rd, "device-manager"));

    // End-to-end proof, read from kernel state — not the racy last-write serial buffer,
    // since many services keep logging after pci-bus. The manager must discover the PCI
    // host bridge, match pci-bus, and spawn it, and pci-bus must come up: claim the
    // bridge, map its ECAM, and register the functions it enumerates as children in the
    // device tree.
    scheduler.setPriority(1);
    const deadline = architecture.millis() + 10000;
    var spawned = false;
    while (architecture.millis() < deadline) {
        if (processRunning("pci-bus")) spawned = true;
        if (spawned and pciFunctionsRegistered()) break;
        scheduler.yield();
    }
    scheduler.setPriority(4);

    check("device manager discovered the PCI host bridge and spawned pci-bus", spawned);
    check("pci-bus came up and registered the functions it enumerated", pciFunctionsRegistered());
    check("its syscalls came from user mode (CPL 3)", process.write_from_user);
    result();
}

/// Whether a live task was spawned under `name` (its argv[0]) — read from the kernel
/// task table, the same snapshot `process_enumerate` exposes.
fn processRunning(name: []const u8) bool {
    var table: [64]abi.ProcessDescriptor = undefined;
    const total = scheduler.enumerate(&table);
    for (table[0..@min(total, table.len)]) |d| {
        // Task names are full binary paths; callers pass either form.
        if (std.mem.eql(u8, initial_ramdisk.basename(d.name[0..d.name_length]), name)) return true;
    }
    return false;
}

/// Whether pci-bus registered at least one function under the PCI host bridge — proof
/// it came up, claimed the bridge, mapped its ECAM, and walked configuration space.
fn pciFunctionsRegistered() bool {
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    const n = @min(devices_broker.enumerate(&buffer), buffer.len);
    var bridge_id: ?u64 = null;
    for (buffer[0..n]) |d| {
        if (d.class == @intFromEnum(device_abi.DeviceClass.pci_host_bridge)) bridge_id = d.id;
    }
    const bid = bridge_id orelse return false;
    for (buffer[0..n]) |d| {
        if (d.parent == bid) return true;
    }
    return false;
}

/// Device id of the kernel-seeded HPET timer block (the node with a memory resource),
/// from the same device table drivers see. Used as a containment-test parent.
fn hpetDeviceId() ?u64 {
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    const n = @min(devices_broker.enumerate(&buffer), buffer.len);
    for (buffer[0..n]) |d| {
        if (d.class != @intFromEnum(device_abi.DeviceClass.timer)) continue;
        if (d.parent != device_abi.no_parent) continue; // a comparator child, not the block
        for (0..d.resource_count) |j| {
            if (d.resources[j].kind == @intFromEnum(device_abi.ResourceKind.memory)) return d.id;
        }
    }
    return null;
}

/// IRQ teardown. When a driver exits, its bindings must be released: the line masked
/// (so a dead driver's device goes quiet instead of storming) and the slot cleared
/// (so an ISR never posts a notification into the endpoint that is about to be freed).
///
/// A long-running driver that never exits wouldn't reach this teardown path, so it
/// gets its own test that binds and releases directly. Two properties, both read back
/// from the hardware rather than from our own state:
///
///   1. A bound GSI is routed and unmasked.
///   2. After `releaseOwner` for the binding's owner, that same entry is masked again.
///
/// And one property that can only be checked from kernel state: a *different* owner's
/// binding on the same endpoint survives. Endpoints are shared (ipc_register hands out
/// references), so teardown keyed on the endpoint pointer rather than the owning task
/// would mask a live sibling driver's device line.
fn irqFreeTest() void {
    log("DANOS-TEST-BEGIN: irqfree\n", .{});

    const gsi = hpetGsi() orelse {
        check("discovery recorded an IRQ resource for the HPET", false);
        result();
        return;
    };

    const endpoint = ipcsync.createIpcEndpoint() orelse {
        check("allocated an endpoint", false);
        result();
        return;
    };

    // Two owners, one shared endpoint. `other` binds a second line if the I/O APIC has
    // one spare; if not, the sharing half of the test is skipped rather than faked.
    const owner: u32 = 4242;
    const other: u32 = 4343;
    const spare: ?u32 = if (gsi + 1 < irq.maximum_gsi and architecture.irqOwnsGsi(gsi + 1)) gsi + 1 else null;

    {
        const flags = sync.enter();
        defer sync.leave(flags);
        irq.bind(gsi, endpoint, owner) catch {};
        if (spare) |s| irq.bind(s, endpoint, other) catch {};
    }
    check("bound GSI is routed and unmasked", !entryMasked(gsi));
    if (spare) |s| check("second owner's GSI is routed and unmasked", !entryMasked(s));

    {
        const flags = sync.enter();
        defer sync.leave(flags);
        irq.releaseOwner(owner);
    }
    check("exiting owner's line is masked again", entryMasked(gsi));
    if (spare) |s| {
        check("a sibling owner's binding on the same endpoint survives", !entryMasked(s));
        const flags = sync.enter();
        defer sync.leave(flags);
        irq.releaseOwner(other);
    }

    result();
}

/// Is redirection entry `gsi` masked? (bit 16 of the low dword; entry index == GSI
/// because this I/O APIC's gsi_base is 0.)
fn entryMasked(gsi: u32) bool {
    return architecture.irqRouteRaw(gsi) & (1 << 16) != 0;
}

/// The MMIO-grant teardown fix: a device-granted leaf must NOT be returned to the
/// RAM allocator when its address space is destroyed. Map a real RAM frame as a
/// device grant, tear the address space down, and confirm the frame is still held
/// (only the page tables came back) — then free it explicitly. A regression guard
/// for the freeSubtree device_grant skip that keeps IO passthrough from corrupting
/// the frame pool.
fn ioPassTest() void {
    log("DANOS-TEST-BEGIN: iopass\n", .{});
    const base_free = pmm.stats().free_frames;

    const address_space = architecture.createAddressSpace() orelse {
        check("created a fresh address space", false);
        result();
        return;
    };
    const frame = pmm.alloc() orelse {
        architecture.destroyAddressSpace(address_space);
        check("allocated a frame to grant", false);
        result();
        return;
    };
    // Map it the way mmio_map does (device grant, strong-uncacheable), then tear the space down.
    architecture.mapUserDeviceInto(address_space, process.device_arena_base, frame, abi.page_size, false);
    architecture.destroyAddressSpace(address_space);

    // The page tables were reclaimed; the device-granted frame must not have been.
    check("device-granted frame survived teardown (not reclaimed as RAM)", pmm.stats().free_frames == base_free - 1);
    pmm.free(frame);
    check("no leak once the frame is explicitly freed", pmm.stats().free_frames == base_free);
    result();
}

/// D1 — the framebuffer handoff primitive. The kernel seeds the loader's framebuffer as
/// a claimable `display` device with a write-combining `memory` resource; a display
/// service reaches it over the ordinary claim + mmio_map path. Prove the whole chain:
/// the node is present and correctly shaped, it maps, and — the point of D1 — the
/// mapping is genuinely write-combining, not the strong-uncacheable default that would
/// make a framebuffer blit glacial.
fn displayTest(boot_information: *const BootInformation) void {
    log("DANOS-TEST-BEGIN: display\n", .{});
    const fb = boot_information.framebuffer;
    if (!fb.present()) {
        // Headless: nothing to seed. Not a failure of the mechanism, so pass cleanly.
        log("display: no framebuffer (headless); skipping\n", .{});
        result();
        return;
    }

    // The kernel seeded a display device in kmain, right after devices_broker.init.
    const display_id = devices_broker.displayDevice() orelse {
        check("a framebuffer display device was seeded", false);
        result();
        return;
    };
    var buffer: [64]device_abi.DeviceDescriptor = undefined;
    const n = @min(devices_broker.enumerate(&buffer), buffer.len);
    check("the seeded display id is enumerable", display_id < n);
    if (display_id >= n) {
        result();
        return;
    }
    const d = buffer[@intCast(display_id)];

    check("the node is class display", d.class == @intFromEnum(device_abi.DeviceClass.display));
    check("it carries the framebuffer geometry", d.display.width == fb.width and d.display.height == fb.height and d.display.pitch == fb.pitch);
    check("it carries the panel refresh rate", d.display.refresh_hz == fb.refresh_hz);
    check("it has exactly one resource", d.resource_count == 1);
    const r = d.resources[0];
    check("that resource is a memory window", r.kind == @intFromEnum(device_abi.ResourceKind.memory));
    check("it spans the whole framebuffer", r.start == fb.base and r.len == @as(u64, fb.height) * fb.pitch);
    check("it is flagged write-combining", (r.flags & device_abi.resource_flag_write_combining) != 0);

    // Walk the real claim + map path a display service would, into a throwaway address
    // space, and confirm the leaf's cache type. We never run this space (no CR3 load) —
    // we only read back the page-table entries — so aliasing the same physical page at
    // two cache types below is inert.
    const address_space = architecture.createAddressSpace() orelse {
        check("created a fresh address space", false);
        result();
        return;
    };
    defer architecture.destroyAddressSpace(address_space);

    const page_base = fb.base & ~@as(u64, abi.page_size - 1);
    architecture.mapUserDeviceInto(address_space, process.device_arena_base, page_base, abi.page_size, true);
    check(
        "the framebuffer maps write-combining (PAT entry 4: PAT bit set, PCD/PWT clear)",
        architecture.userLeafIsWriteCombining(address_space, process.device_arena_base) == true,
    );

    // Regression guard: the strong-uncacheable default is still that, so WC is a real
    // choice the flag makes, not the only behaviour.
    architecture.mapUserDeviceInto(address_space, process.device_arena_base + abi.page_size, page_base, abi.page_size, false);
    check(
        "a register window still maps strong-uncacheable",
        architecture.userLeafIsWriteCombining(address_space, process.device_arena_base + abi.page_size) == false,
    );

    log("display: mapped {d}x{d} pitch {d} (write-combining)\n", .{ fb.width, fb.height, fb.pitch });
    result();
}

fn faultInvalidOpcode() void {
    log("DANOS-TEST-BEGIN: fault-ud\n", .{});
    asm volatile ("ud2");
}

/// Verify NX: fetching an instruction from a data page (mapped no-execute) faults.
fn faultNoExecute() void {
    log("DANOS-TEST-BEGIN: fault-nx\n", .{});
    var scratch: u64 = 0xC3; // a lone `ret` — harmless if NX somehow let it run
    const f: *const fn () void = @ptrFromInt(@intFromPtr(&scratch));
    f(); // instruction fetch from an NX page -> #PF before it executes
    log("DANOS-TEST-RESULT: FAIL (NX not enforced)\n", .{});
}

/// Verify the null guard: dereferencing address 0 (page 0 left unmapped) faults.
fn faultNull() void {
    log("DANOS-TEST-BEGIN: fault-null\n", .{});
    // Launder the address through empty asm so the compiler no longer knows it's
    // 0 (otherwise it folds a null-pointer safety panic instead of doing the real
    // access). `allowzero` skips the same null check on the cast. The write then
    // hits the unmapped page 0 and takes a real hardware #PF.
    var address: u64 = 0;
    address = asm (""
        : [ret] "=r" (-> u64),
        : [in] "0" (address),
    );
    const p: *allowzero volatile u64 = @ptrFromInt(address);
    p.* = 1;
}

fn faultPageFault() void {
    log("DANOS-TEST-BEGIN: fault-pf\n", .{});
    // Runtime address so the backend emits a register store (not a `mov moffs`,
    // which the self-hosted x86_64 backend can't encode).
    var address: u64 = 0xdeadbeef000; // well above all mapped RAM
    const p: *volatile u64 = @ptrFromInt(address);
    p.* = 1;
    address += 0;
}

fn faultDoubleFault() void {
    log("DANOS-TEST-BEGIN: fault-df\n", .{});
    architecture.disableInterrupts(); // so only the ud2 delivery (not a timer tick) triggers the #DF
    // Point RSP at unmapped memory, then fault: the CPU can't push the fault
    // frame, which escalates to #DF — survivable only because #DF runs on IST1.
    var bad_sp: u64 = 0x5000000000;
    asm volatile (
        \\mov %[sp], %%rsp
        \\ud2
        :
        : [sp] "r" (bad_sp),
        : .{ .memory = true });
    bad_sp += 0;
}

var ap_reached_fault: bool = false;

/// A task that faults with a #DF *on whatever core it's pinned to*. Announces the
/// core, then triggers the same double fault as `faultDoubleFault` — which is only
/// survivable on IST1, so it exercises that core's own TSS.
fn apDoubleFaultTask() void {
    log("DANOS-AP: task running on core {d}, triggering #DF\n", .{scheduler.currentCpuIndex()});
    @atomicStore(bool, &ap_reached_fault, true, .release);
    architecture.disableInterrupts();
    var bad_sp: u64 = 0x5000000000;
    asm volatile (
        \\mov %[sp], %%rsp
        \\ud2
        :
        : [sp] "r" (bad_sp),
        : .{ .memory = true });
    bad_sp += 0;
}

/// Fault on an application processor. Pins a double-faulting task to core 1, so the
/// fault is taken and handled by *that core's own* IDT and TSS/IST — not the BSP's.
/// The harness matches "core N: double fault (vector 8)" with N ≥ 1, which can only
/// appear if the AP caught the #DF on its IST1 (a broken per-core TSS would
/// triple-fault and reset instead). We then show the BSP still runs afterwards, so
/// the fault was *contained* to the AP, not fatal to the system.
fn faultApTest() void {
    log("DANOS-TEST-BEGIN: fault-ap-df\n", .{});
    if (!scheduler.spawnOn(apDoubleFaultTask, 6, 1)) {
        log("DANOS-AP: could not pin to core 1 (run with -smp) - FAIL\n", .{});
        architecture.halt();
    }
    // Wait until the AP is about to fault, then keep running to prove containment.
    var spins: u64 = 0;
    while (!@atomicLoad(bool, &ap_reached_fault, .acquire) and spins < 5_000_000_000) spins +%= 1;
    var settle: u64 = 0;
    while (settle < 500_000_000) settle +%= 1; // let the AP take + report the fault
    log("DANOS-BSP: core {d} still running after the AP fault (contained)\n", .{scheduler.currentCpuIndex()});
    architecture.halt();
}
