a73x

src/cli/main.zig

Ref:   Size: 113.3 KiB   History

//! `mux d` — the daemon mode. `start` hosts the session; `dump` prints the
//! authoritative grid over the protocol (debug aid, also used by e2e);
//! `proxy` exposes the session socket over stdio for `mux --via`.
// Rationale: spawning the user's shell is the daemon's purpose;
// `/bin/sh` is the executable fallback when `$SHELL` is unset.

const std = @import("std");
const builtin = @import("builtin");
const Server = @import("daemon").Server;
const proto = @import("term").protocol;
const proxy = @import("proxy");
const quic = @import("quic");
const quic_server = @import("daemon").quic_server;
const build_options = @import("build_options");
const xdg = @import("xdg");
const spawn = @import("spawn");
const server_os = @import("server_os");
const handoff = @import("client").handoff;
const sockpath = @import("sockpath");
const upgrade = @import("daemon").upgrade;
const cliflags = @import("cliflags");
const dial = @import("dial");
const link_mod = @import("link");

const usage =
    \\usage:
    \\  mux d start [-d] [--sock PATH] [--shell PATH] [--cols N] [--rows N]
    \\              [--quic HOST[:PORT] --key FILE] [--quic-idle-ms N]
    \\              (-d comes first; forks it off and waits, no-op if up)
    \\  mux d dump  [--vt] [--session NAME] [--sock PATH | --quic HOST[:PORT] [--key FILE]]
    \\  mux d stats [--sock PATH | --quic HOST[:PORT] [--key FILE]]
    \\  mux d stop  [--sock PATH | --quic HOST[:PORT] [--key FILE]]  (ask the daemon to exit)
    \\  mux d proxy [--sock PATH]    (byte pump: stdio <-> session socket)
    \\  mux d endpoint [--sock PATH] [--start] (announce QUIC port+key, then proxy)
    \\  mux d keygen                 (write a fresh key to ~/.config/mux/key)
    \\  mux d upgrade [HOST] [--sock PATH] (exec THIS binary over the daemon; sessions
    \\        live. HOST: push this image there over ssh, then upgrade its daemon)
    \\        [--allow-same-version] (strictly newer, unless this; the e2e leg's)
    \\  mux d --version
    \\  mux d --help
    \\
;

const DaemonCommand = enum { dump, stats, proxy, endpoint, version, help, keygen, start, stop, upgrade };

/// Metadata for one daemon command. Dispatch-independent behavior is derived
/// from this table and checked for completeness at compile time.
const CommandSpec = struct {
    name: []const u8,
    cmd: DaemonCommand,
    /// Whether the command uses the resolved socket path. Commands that do not
    /// touch a socket must not fail because the default path is invalid.
    uses_socket: bool,
    /// How trailing arguments are handled. `all` runs the shared parser,
    /// `none` rejects every argument, and `ignored` skips parsing entirely.
    /// The ignored behavior is compatibility-tested for help and version.
    flags: enum { none, all, ignored },
};

const specs = [_]CommandSpec{
    // Table order has no effect because lookup is by exact name. Version and
    // help are represented as commands even though users spell them as flags.
    .{ .name = "--version", .cmd = .version, .uses_socket = false, .flags = .ignored },
    // Help ignores adjacent arguments so requested usage is not replaced by a
    // syntax diagnostic.
    .{ .name = "--help", .cmd = .help, .uses_socket = false, .flags = .ignored },
    .{ .name = "start", .cmd = .start, .uses_socket = true, .flags = .all },
    .{ .name = "dump", .cmd = .dump, .uses_socket = true, .flags = .all },
    .{ .name = "stats", .cmd = .stats, .uses_socket = true, .flags = .all },
    .{ .name = "proxy", .cmd = .proxy, .uses_socket = true, .flags = .all },
    .{ .name = "endpoint", .cmd = .endpoint, .uses_socket = true, .flags = .all },
    // `keygen` accepts no options because it always writes the default path.
    .{ .name = "keygen", .cmd = .keygen, .uses_socket = false, .flags = .none },
    .{ .name = "stop", .cmd = .stop, .uses_socket = true, .flags = .all },
    .{ .name = "upgrade", .cmd = .upgrade, .uses_socket = true, .flags = .all },
};

comptime {
    for (std.enums.values(DaemonCommand)) |c| {
        var rows = 0;
        for (specs) |s| {
            if (s.cmd == c) rows += 1;
        }
        // Require exactly one row per command. Duplicate rows would make one
        // name unreachable and make `specForCmd` depend on table order.
        if (rows == 0) @compileError("DaemonCommand has no row in specs: " ++ @tagName(c));
        if (rows > 1) @compileError("DaemonCommand has more than one row in specs: " ++ @tagName(c));
    }
}

/// Look up an exact command name; prefixes such as `ru` remain syntax errors.
fn specForName(name: []const u8) ?CommandSpec {
    for (specs) |s| {
        if (std.mem.eql(u8, name, s.name)) return s;
    }
    return null;
}

/// Return the row for a command. The compile-time table check makes failure
/// unreachable.
fn specForCmd(cmd: DaemonCommand) CommandSpec {
    for (specs) |s| {
        if (s.cmd == cmd) return s;
    }
    unreachable;
}

/// Parsed arguments for a daemon command. Parsing is separate from `main` so
/// syntax and defaults can be unit tested without process exit.
const DaemonArguments = struct {
    /// Selected before flag parsing; the leading underscore excludes it from
    /// generated flags.
    _cmd: DaemonCommand,
    /// Set only when `start` has `-d` or `--detach` in its first argument
    /// position. True starts a child and waits for its socket; false runs in the
    /// foreground.
    _detach: bool = false,
    sock: ?[]const u8 = null,
    shell: ?[]const u8 = null,
    cols: u16 = 80,
    rows: u16 = 24,
    vt: bool = false,
    quic: ?[]const u8 = null,
    /// An explicit key path. Null still allows `MUX_KEY_FILE` or the default
    /// path to be resolved later. `parseArgs` rejects only the inverse case,
    /// `--key` without `--quic`.
    key: ?[]const u8 = null,
    /// `quic.IdleMs` rejects zero and values outside u32.
    quic_idle_ms: quic.IdleMs = .{},
    /// Session selected by `dump`. Validation applies only to an explicitly
    /// supplied name; the wire uses an empty tail for the default session.
    session: ?proto.SessionName = null,
    /// Manifest descriptor supplied by the old daemon during upgrade. Its
    /// presence makes `start` adopt existing state instead of resolving a new
    /// socket path.
    resume_fd: ?std.posix.fd_t = null,
    /// Validate the complete manifest without adopting it. The old daemon uses
    /// this child-process check before relinquishing service.
    check: bool = false,
    /// Test-only failure injection after the named manifest section. It verifies
    /// rollback after a candidate has read the manifest but cannot adopt it.
    resume_fail_at: ?[]const u8 = null,
    /// Allow the upgrade e2e test to reuse the same binary instead of requiring
    /// a strictly newer version.
    allow_same_version: bool = false,
    /// `endpoint`-only option that starts the daemon before announcing it. A
    /// direct cold attach uses this flag; background polls omit it and remain
    /// read-only.
    start: bool = false,
    /// `upgrade`'s one positional: the ssh host to push this image to and
    /// upgrade there. Underscored out of generated flags; every other verb
    /// leaves the hook cold so a bare word stays a syntax error on them.
    _host: ?[]const u8 = null,

    pub fn positional(self: *DaemonArguments, word: []const u8) bool {
        if (self._cmd != .upgrade or self._host != null) return false;
        self._host = word;
        return true;
    }
};

// These upgrade-only flags are generated by the old daemon and intentionally
// omitted from user-facing usage text.
comptime {
    cliflags.assertDocumented(DaemonArguments, usage, &.{ "resume_fd", "check", "resume_fail_at" });
}

/// Usage or help response produced instead of executing a daemon command.
/// Invalid syntax is reported without a stack trace; help exits successfully.
const UsageResponse = union(enum) {
    no_command,
    /// Requested help, the only response that exits successfully.
    help,
    unknown_command: []const u8,
    unknown_arg: []const u8,
    /// A flag at the end of argv with nothing left to consume.
    missing_value: []const u8,
    /// The flag whose value its own type would not hold.
    bad_value: []const u8,
    key_without_quic,
    /// `upgrade HOST --sock PATH`: the socket that matters on a remote
    /// upgrade is the remote box's own default, and a --sock honored
    /// locally while HOST is honored remotely would leave the user
    /// believing both.
    sock_with_host,
    /// `stop|dump|stats --sock PATH --quic HOST:PORT`: two daemons named
    /// for one question. `start` takes both, because a daemon can listen
    /// on both; the admin verbs ask one thing of one daemon.
    sock_with_quic,
};

const DaemonInvocation = union(enum) { command: DaemonArguments, usage: UsageResponse };

/// The verbs that may reach a daemon over QUIC instead of its socket path:
/// one frame in, one frame out, which `handleDaemonVerb` already serves on a
/// QUIC client slot. `upgrade` stays on the socket — its request is served
/// to observers only, and the manifest it carries is local to the box.
fn quicAdmin(cmd: DaemonCommand) bool {
    return cmd == .stop or cmd == .dump or cmd == .stats;
}

fn parseArgs(args: []const [:0]const u8) DaemonInvocation {
    if (args.len < 2) return .{ .usage = .no_command };
    const spec = specForName(args[1]) orelse return .{ .usage = .{ .unknown_command = args[1] } };

    // The one place a verb's flag class is enforced; which class each verb
    // is in is stated once, in its row.
    switch (spec.flags) {
        .ignored => return .{ .command = .{ ._cmd = spec.cmd } },
        .none => if (args.len > 2) return .{ .usage = .{ .unknown_arg = args[2] } },
        .all => {},
    }

    var o: DaemonArguments = .{ ._cmd = spec.cmd };

    // `-d` is recognized only in the first argument position after `start`.
    // Keeping it outside the generic flag struct makes it unknown for every
    // other command without separate checks.
    const flag_args = if (spec.cmd == .start and args.len > 2 and
        (std.mem.eql(u8, args[2], "-d") or std.mem.eql(u8, args[2], "--detach")))
    blk: {
        o._detach = true;
        break :blk args[3..];
    } else args[2..];

    switch (cliflags.parse(DaemonArguments, &o, flag_args)) {
        .ok => {},
        .help => return .{ .usage = .help },
        // Version takes precedence anywhere on the line and requires neither a
        // runtime directory nor a running daemon.
        .version => return .{ .command = .{ ._cmd = .version } },
        .unknown_arg => |a| return .{ .usage = .{ .unknown_arg = a } },
        .missing_value => |f| return .{ .usage = .{ .missing_value = f } },
        .bad_value => |f| return .{ .usage = .{ .bad_value = f } },
    }

    // Reject `--start` on every command except `endpoint`, including the likely
    // typo `mux d start --start`.
    if (o.start and spec.cmd != .endpoint) return .{ .usage = .{ .unknown_arg = "--start" } };

    // `--key` without `--quic` is always invalid. The reverse is valid because
    // `run` may resolve `MUX_KEY_FILE` or the default key path.
    if (o.key != null and o.quic == null) return .{ .usage = .key_without_quic };

    if (o._host != null and o.sock != null) return .{ .usage = .sock_with_host };
    if (quicAdmin(spec.cmd) and o.quic != null and o.sock != null) return .{ .usage = .sock_with_quic };
    // `--quic` is a bind for `start` and a door for the admin verbs. Every
    // other verb is served on the socket alone, and a flag it parsed and
    // dropped would run `mux d upgrade --quic HOST` against the socket's
    // daemon while the user believed HOST was being upgraded.
    if (o.quic != null and spec.cmd != .start and !quicAdmin(spec.cmd)) return .{ .usage = .{ .unknown_arg = "--quic" } };

    return .{ .command = o };
}

/// Return the exit code for a usage response without printing it.
fn usageCode(u: UsageResponse) u8 {
    return if (u == .help) 0 else 2;
}

fn usageExit(u: UsageResponse) u8 {
    switch (u) {
        .help => return cliflags.help(usage),
        .no_command => std.debug.print("{s}", .{usage}),
        .unknown_command => std.debug.print("{s}", .{usage}),
        .unknown_arg => |a| std.debug.print("unknown argument: {s}\n{s}", .{ a, usage }),
        .missing_value => |f| std.debug.print("mux d: {s} needs a value\n{s}", .{ f, usage }),
        .bad_value => |f| std.debug.print("mux d: {s} was given a value it cannot hold\n{s}", .{ f, usage }),
        .key_without_quic => std.debug.print(
            "mux d: --key without --quic has nothing to listen on; name both or neither\n",
            .{},
        ),
        .sock_with_host => std.debug.print(
            "mux d: upgrade HOST uses that box's own default socket; --sock only names a local one\n",
            .{},
        ),
        .sock_with_quic => std.debug.print(
            "mux d: --sock and --quic name two daemons; ask one of them\n",
            .{},
        ),
    }
    return usageCode(u);
}

/// Parse a numeric bind address without DNS; a hostname may resolve to several
/// addresses and does not identify one deterministic bind target.
fn parseBindAddr(s: []const u8) !std.net.Address {
    const hp = try quic.splitHostPort(s);
    return std.net.Address.parseIp(hp.host, hp.port);
}

/// Run daemon mode using the argv slice supplied by the top-level dispatcher.
pub fn main(args: []const [:0]const u8) !u8 {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    defer if (gpa.deinit() == .leak)
        std.debug.print("mux d: LEAK: allocations outlived deinit\n", .{});
    const alloc = gpa.allocator();

    const o = switch (parseArgs(args)) {
        .usage => |u| return usageExit(u),
        .command => |o| o,
    };

    // Resolve a socket path only for commands that use one. Version, help, and
    // key generation must not depend on runtime-directory state. Resume mode
    // receives its already-bound path through the manifest.
    // A remote upgrade names a HOST, and the socket that matters is that
    // box's own default; resolving a local one here would refuse the verb on
    // a machine with no XDG_RUNTIME_DIR for no reason it could name.
    // An admin verb aimed at `--quic` resolves no socket path either: the
    // daemon it is for may have none, and the box asking may have no
    // runtime dir.
    const uses_socket = specForCmd(o._cmd).uses_socket and o.resume_fd == null and o._host == null and
        !(quicAdmin(o._cmd) and o.quic != null);
    const sock_path = if (o.sock) |s|
        try alloc.dupe(u8, s)
    else if (!uses_socket)
        try alloc.dupe(u8, "")
    else
        try sockpath.defaultOrExplain(alloc, "mux d") orelse return 1;
    defer alloc.free(sock_path);

    // Reject an overlong Unix socket path before executing a command; otherwise
    // detached startup would fail later as a misleading timeout. Command
    // metadata determines which commands are exempt.
    if (uses_socket and sockpath.tooLong("mux d", sock_path)) return 1;

    switch (o._cmd) {
        // Version does not use or validate the resolved socket path.
        .version => return cliflags.version("mux", build_options.version),
        .help => return usageExit(.help),
        .keygen => return keygen(alloc),
        .start => return if (o.resume_fd) |fd|
            resumeRun(alloc, o, fd)
        else
            startCmd(alloc, o, sock_path, args[2..]),
        .dump => return dump(alloc, adminTarget(o, sock_path), o.vt, if (o.session) |n| n.name else ""),
        .stats => return stats(alloc, adminTarget(o, sock_path)),
        .stop => return stopCmd(alloc, adminTarget(o, sock_path)),
        .upgrade => return if (o._host) |h|
            remoteUpgradeCmd(alloc, h, o.allow_same_version)
        else
            upgradeCmd(alloc, sock_path, o.allow_same_version),
        // Proxy only pumps an existing daemon connection; it never starts a
        // daemon. `proxy.run` reports the socket path when connection fails.
        .proxy => return proxy.run(sock_path),
        .endpoint => return endpointCmd(alloc, sock_path, std.posix.STDOUT_FILENO, o.start),
    }
}

/// Enable shell integration only for the exact value `1`; the integration
/// changes zsh startup and uses Bash's DEBUG trap.
fn shellIntegrationEnabled(env: ?[]const u8) bool {
    return std.mem.eql(u8, env orelse "", "1");
}

/// Maximum upgrade manifest size: large enough for the full session table but
/// bounded so an invalid descriptor cannot cause unbounded allocation.
const manifest_read_max = 64 * 1024 * 1024;

/// Rollback marker read by the next adoption attempt. It prevents two binaries
/// that reject the same manifest from repeatedly executing each other. An
/// environment variable remains compatible with older rollback targets that
/// would reject an unknown flag.
const rollback_marker = "MUX_UPGRADE_ROLLBACK";

/// Environment equivalent of the test-only `--resume-fail-at` option. Upgrade
/// exec builds fixed argv but preserves this variable for e2e failure injection.
const fail_at_env = "MUX_RESUME_FAIL_AT";

/// Prefer the explicit failure-injection flag over its environment fallback.
fn failAtFrom(flag: ?[]const u8, env: ?[]const u8) []const u8 {
    return flag orelse env orelse "";
}

/// What the rollback exec keeps from this process's environment.
fn rollbackKeepsEnv(entry: []const u8) bool {
    // The abort that caused the rollback must not be inherited by the
    // binary being rolled back TO: it would abort at the same section,
    // find the marker, give up, and take every shell with it.
    return !std.mem.startsWith(u8, entry, fail_at_env ++ "=");
}

/// libc declaration used to remove the rollback marker before serving. Leaving
/// it set would disable a later rollback and propagate it to spawned shells.
extern "c" fn unsetenv(name: [*:0]const u8) c_int;

/// Execute the previous daemon binary with the still-open manifest descriptor.
fn rollback(
    alloc: std.mem.Allocator,
    writer_path: []const u8,
    resume_fd: std.posix.fd_t,
    section: []const u8,
) u8 {
    // Rollback occurs before serving begins, while all inherited descriptors
    // remain open and the manifest still identifies the previous binary.
    std.debug.print(
        "mux d start: adoption failed at {s}; exec'ing {s} back\n",
        .{ section, writer_path },
    );
    if (std.posix.getenv(rollback_marker) != null) {
        std.debug.print(
            "mux d start: this IS the rollback ({s} refused the manifest it wrote); giving up\n",
            .{writer_path},
        );
        return 1;
    }

    // The manifest was read to EOF and the descriptor is the one being
    // passed on: the next binary starts where the writer left it.
    var file = std.fs.File{ .handle = resume_fd };
    file.seekTo(0) catch {};

    var fd_buf: [12]u8 = undefined;
    const fd_str = std.fmt.bufPrintZ(&fd_buf, "{d}", .{resume_fd}) catch return 1;
    // `d start`, the only spelling there is. The binary being exec'd back is
    // older than this one, but not by more than the rename: a daemon of
    // v0.0.1-15 or earlier refuses this binary as a candidate at its
    // version probe, so it can never have been the writer here.
    const argv = [_:null]?[*:0]const u8{ "mux", "d", "start", "--resume-fd", fd_str.ptr };
    const path_z = alloc.dupeZ(u8, writer_path) catch return 1;
    const envp = rollbackEnvp(alloc) catch return 1;

    // Successful exec does not return. If rollback exec fails, process exit
    // closes PTY masters and sends the same SIGHUP effect as stop followed by
    // a fresh run.
    const exec_err = std.posix.execveZ(path_z.ptr, &argv, envp);
    alloc.free(path_z);
    alloc.free(std.mem.span(envp));
    std.debug.print(
        "mux d start: rollback exec of {s} failed: {s}\n",
        .{ writer_path, @errorName(exec_err) },
    );
    return 1;
}

/// Return this process's environment plus the rollback marker. The allocation
/// intentionally lives until the immediately following execve.
fn rollbackEnvp(alloc: std.mem.Allocator) ![*:null]const ?[*:0]const u8 {
    var n: usize = 0;
    while (std.c.environ[n] != null) n += 1;
    const envp = try alloc.allocSentinel(?[*:0]const u8, n + 1, null);
    var kept: usize = 0;
    for (0..n) |i| {
        const entry = std.c.environ[i].?;
        if (!rollbackKeepsEnv(std.mem.span(entry))) continue;
        envp[kept] = entry;
        kept += 1;
    }
    envp[kept] = rollback_marker ++ "=1";
    kept += 1;
    // The dropped entries leave a tail of undefined pointers between the
    // last kept one and the sentinel; execve reads to the first null.
    for (envp[kept..]) |*slot| slot.* = null;
    return envp.ptr;
}

/// Adopt a daemon from the manifest descriptor supplied by an upgrade exec.
/// The process keeps its pid, children, and descriptors. Reading directly from
/// the carrier, which no path names, also keeps the embedded QUIC key off disk.
fn resumeRun(alloc: std.mem.Allocator, o: DaemonArguments, resume_fd: std.posix.fd_t) !u8 {
    // The writer left the offset at the end of what it wrote, and a child
    // shares the file description with it, so the rewind is ours to do.
    var file = std.fs.File{ .handle = resume_fd };
    file.seekTo(0) catch |err| {
        std.debug.print("mux d start: --resume-fd {d} does not seek ({t})\n", .{ resume_fd, err });
        return 1;
    };
    const bytes = file.readToEndAlloc(alloc, manifest_read_max) catch |err| {
        std.debug.print("mux d start: cannot read the manifest on fd {d} ({t})\n", .{ resume_fd, err });
        return 1;
    };
    defer alloc.free(bytes);

    var parsed = upgrade.parseManifest(alloc, bytes) catch |err| {
        std.debug.print("mux d start: manifest on fd {d} is not one ({t})\n", .{ resume_fd, err });
        return 1;
    };
    defer parsed.deinit();

    // `--check` validates the complete manifest in a child and adopts nothing.
    // It precedes failure injection because a validation child must never roll
    // back or replace the live daemon.
    if (o.check) return 0;

    const fail_at = failAtFrom(o.resume_fail_at, std.posix.getenv(fail_at_env));
    if (std.mem.eql(u8, fail_at, "daemon"))
        return rollback(alloc, parsed.daemon.writer_path, resume_fd, "daemon (--resume-fail-at)");

    var srv = Server.initFromManifest(alloc, &parsed, build_options.version) catch |err| {
        var reason: [64]u8 = undefined;
        return rollback(alloc, parsed.daemon.writer_path, resume_fd, std.fmt.bufPrint(&reason, "sessions ({t})", .{err}) catch "sessions");
    };
    // Before the teardown defer, never after: a rollback must leave every
    // shell, socket and directory exactly as it found them, and deinit is
    // the demolition list.
    if (std.mem.eql(u8, fail_at, "session"))
        return rollback(alloc, parsed.daemon.writer_path, resume_fd, "session (--resume-fail-at)");

    // The last rollback point is behind us, so the descriptors it would have
    // handed back are this image's to keep — and to stop handing on. Rollback
    // was the manifest's last reader; left open, it is CLOEXEC-cleared like
    // the rest and every shell spawned from here inherits it.
    std.posix.close(resume_fd);
    srv.sealAdoptedFds();

    defer srv.deinit();

    // The marker's job ended the moment this image started serving.
    _ = unsetenv(rollback_marker);

    @import("daemon").installSignalHandlers();
    return try srv.run();
}

/// Run the daemon in the foreground. Startup failures occur before serving;
/// once `srv.run` is reached, the daemon returns zero when service ends.
fn run(alloc: std.mem.Allocator, o: DaemonArguments, sock_path: []const u8) !u8 {
    // Resolve the QUIC address and key before binding or starting a shell so
    // configuration errors leave no session socket or child process behind.
    var quic_bind: ?std.net.Address = null;
    var quic_key: quic.Key = undefined;
    if (o.quic) |hostport| {
        quic_bind = parseBindAddr(hostport) catch {
            std.debug.print(
                "mux d: --quic wants HOST:PORT with a literal address, got {s}\n",
                .{hostport},
            );
            return 1;
        };
        // Resolve `--key`, then `MUX_KEY_FILE`, then the default path, matching
        // direct QUIC clients. Only QUIC-enabled startup requires a key.
        const key_res = try xdg.resolveKeyPath(alloc, xdg.pickKey(o.key, std.posix.getenv(xdg.key_env)));
        defer key_res.deinit(alloc);
        const key_path = switch (key_res) {
            .given, .default => |p| p,
            .missing => |p| {
                std.debug.print(
                    "mux d: no key: pass --key, set MUX_KEY_FILE, or run `mux d keygen` (default {s})\n",
                    .{p},
                );
                return 2;
            },
        };
        quic_key = quic.Key.load(key_path) catch |err| switch (err) {
            // The three the user can act on, in quic.zig's words — its one
            // owner, since three callers print the same sentences. Anything
            // else propagates: `run` is a foreground start that may fail, where
            // an announce path must stay on ssh and so flattens instead.
            error.KeyFileMissing,
            error.KeyFilePermissive,
            error.KeyFileMalformed,
            => {
                var buf: [quic.key_refusal_len]u8 = undefined;
                std.debug.print("mux d: {s}\n", .{quic.keyRefusalBody(&buf, err, key_path)});
                return 1;
            },
            else => return err,
        };
    }

    // Bind UDP before the session socket so a conflicting QUIC port leaves no
    // shell process or Unix socket behind.
    var listener: ?*quic_server.Listener = null;
    if (quic_bind) |addr| {
        listener = quic_server.Listener.bind(alloc, addr, quic_key, o.quic_idle_ms.ms) catch |err| switch (err) {
            // The listener intentionally omits SO_REUSEADDR. Report an occupied
            // QUIC port instead of sharing datagrams with another daemon.
            error.AddressInUse => {
                std.debug.print(
                    "mux d: a daemon is already listening on udp {s}\n",
                    .{o.quic.?},
                );
                return 1;
            },
            else => {
                std.debug.print(
                    "mux d: cannot listen on udp {s}: {s}\n",
                    .{ o.quic.?, @errorName(err) },
                );
                return 1;
            },
        };
    }
    // Registered before the server's, so it runs after it: a client slot
    // backed by QUIC closes through the listener, and the server must finish
    // tearing its slots down before the listener is freed.
    defer if (listener) |l| l.deinit();

    const shell_z: [:0]const u8 = if (o.shell) |s|
        try alloc.dupeZ(u8, s)
    else
        // folder rule 5 exemption: The daemon invokes the session shell directly as argv.
        try alloc.dupeZ(u8, std.posix.getenv("SHELL") orelse "/bin/sh");
    defer alloc.free(shell_z);

    // Shell integration comes from the daemon environment because the daemon
    // starts the session shell before any client can provide options.
    const shell_integration = shellIntegrationEnabled(
        std.posix.getenv("MUX_SHELL_INTEGRATION"),
    );

    var srv = Server.init(alloc, .{
        .sock_path = sock_path,
        .shell = shell_z,
        .cols = o.cols,
        .rows = o.rows,
        .shell_integration = shell_integration,
        .version = build_options.version,
    }) catch |err| switch (err) {
        // All of these mean "that path is not ours to take", and all are
        // operator mistakes: one line, no stack trace. AddressInUse is the same
        // race found one syscall later, and the loser is told the truth — by
        // the time it reads the message, a daemon IS running.
        error.DaemonAlreadyRunning, error.AddressInUse => {
            std.debug.print("mux d: a daemon is already running on {s}\n", .{sock_path});
            return 1;
        },
        error.SockPathNotASocket => {
            std.debug.print(
                "mux d: {s} exists and is not a socket (move it, or name another with --sock)\n",
                .{sock_path},
            );
            return 1;
        },
        else => return err,
    };

    defer srv.deinit();

    if (listener) |l| {
        l.setHandler(srv.quicHandler());
        srv.attachQuic(l);
    }

    @import("daemon").installSignalHandlers();
    return try srv.run();
}

/// Where an admin verb sends its one frame. `sock` is the path every verb
/// has always taken. `quic` is the second door, for the daemon the first
/// cannot reach: one whose socket file was deleted and whose path a
/// successor now holds, so the re-bind in `Server.watchSockPath` is
/// refused and the daemon is still holding sessions nobody can stop,
/// dump or count (issue 145807a2). `mux a` has dialled daemons this way
/// since QUIC existed; the daemon serves these verbs on a client slot
/// exactly as on an observer, so this is the client learning the spelling.
const AdminTarget = union(enum) {
    sock: []const u8,
    quic: struct { host_port: []const u8, key: ?[]const u8 },
};

fn adminTarget(o: DaemonArguments, sock_path: []const u8) AdminTarget {
    if (o.quic) |hp| return .{ .quic = .{ .host_port = hp, .key = o.key } };
    return .{ .sock = sock_path };
}

/// The one bound QUIC asks live under. A unix socket says "nothing
/// listening" as an errno in a microsecond, so `oneShotQuery` on a path
/// may wait forever on a daemon that answered the connect; UDP has no
/// such errno — an unanswered port is silence — so every QUIC ask is
/// bounded, and the silence is reported as what it might be.
const admin_quic_deadline_ms: u32 = 5000;

/// One deadline for the whole ask, minted by the caller and spent by the
/// handshake and the reply together: two full budgets in a row made "5 s"
/// a ten-second wait on a port that took the handshake and nothing else.
fn adminDeadline() i64 {
    return std.time.milliTimestamp() + admin_quic_deadline_ms;
}

fn msLeft(deadline: i64) u32 {
    const left = deadline - std.time.milliTimestamp();
    return if (left <= 0) 0 else @intCast(@min(left, admin_quic_deadline_ms));
}

/// Dial a daemon's QUIC arm for one admin verb: key by the same three-way
/// rule `mux a --quic` and `mux d start --quic` use (`--key`, then
/// `MUX_KEY_FILE`, then the default path), handshake within the deadline.
/// Every refusal is printed here in the verb's voice and answered with
/// null; the caller's only job is the frame.
fn openAdminQuic(alloc: std.mem.Allocator, verb: []const u8, q: anytype, deadline: i64) ?link_mod.Link {
    const res = xdg.resolveKeyPath(alloc, xdg.pickKey(q.key, std.posix.getenv(xdg.key_env))) catch |e| {
        std.debug.print("mux d {s}: quic: cannot resolve a key path: {s}\n", .{ verb, @errorName(e) });
        return null;
    };
    defer res.deinit(alloc);
    const key_path = switch (res) {
        .given, .default => |p| p,
        .missing => |p| {
            std.debug.print(
                "mux d {s}: no key: pass --key, set MUX_KEY_FILE, or run `mux d keygen` (default {s})\n",
                .{ verb, p },
            );
            return null;
        },
    };
    const key = quic.Key.load(key_path) catch |e| {
        var buf: [quic.key_refusal_len]u8 = undefined;
        std.debug.print("mux d {s}: {s}\n", .{ verb, quic.keyRefusalBody(&buf, e, key_path) });
        return null;
    };
    const addr = quic.parseAddr(alloc, q.host_port) catch |e| {
        std.debug.print("mux d {s}: --quic wants HOST[:PORT], got {s}: {s}\n", .{ verb, q.host_port, @errorName(e) });
        return null;
    };
    const cl = quic.Client.connect(alloc, addr, key, quic.default_idle_ms) catch |e| {
        std.debug.print("mux d {s}: cannot dial quic://{s}: {s}\n", .{ verb, q.host_port, @errorName(e) });
        return null;
    };
    while (!cl.isReady()) {
        cl.pump();
        if (cl.dead or std.time.milliTimestamp() >= deadline) {
            cl.deinit();
            std.debug.print(
                "mux d {s}: quic://{s} did not answer in {d}s (nothing there, or not this key)\n",
                .{ verb, q.host_port, admin_quic_deadline_ms / 1000 },
            );
            return null;
        }
        var fds = [_]std.posix.pollfd{
            .{ .fd = cl.pollFd(), .events = std.posix.POLL.IN, .revents = 0 },
        };
        _ = std.posix.poll(&fds, cl.timeoutMs(50)) catch {};
    }
    return .{ .quic = .{ .cl = cl, .alloc = alloc } };
}

/// Perform a one-shot query. On a socket it is unbounded: a connected
/// daemon that stops replying remains a visible hang rather than being
/// reported like an absent socket. Over QUIC it is bounded, for the reason
/// on `admin_quic_deadline_ms`.
fn oneShotQuery(
    alloc: std.mem.Allocator,
    target: AdminTarget,
    verb: []const u8,
    req: proto.MsgType,
    req_payload: []const u8,
    want: proto.MsgType,
) !u8 {
    const frame = switch (target) {
        .sock => |sock_path| (dial.ask(alloc, sock_path, req, req_payload, want, null) catch |e| switch (e) {
            error.NoDaemon => {
                std.debug.print(
                    "mux d {s}: nothing listening on {s} (`mux d start -d` starts one)\n",
                    .{ verb, sock_path },
                );
                return 1;
            },
            // A reply this side could not read, or a daemon that hung up over the
            // request: both stay errors, so a broken daemon never reads as an
            // absent one.
            else => return e,
        }) orelse return 1,
        .quic => |q| blk: {
            const deadline = adminDeadline();
            var l = openAdminQuic(alloc, verb, q, deadline) orelse return 1;
            // The goodbye: a QUIC connection holds one of the daemon's client
            // slots until CONNECTION_CLOSE or its idle timer, and close()
            // writes the former.
            defer l.close();
            l.sendFrame(req, req_payload) catch |e| {
                std.debug.print("mux d {s}: quic: could not send the request: {s}\n", .{ verb, @errorName(e) });
                return 1;
            };
            const got = l.awaitFrame(alloc, want, msLeft(deadline), .{}) catch |e| switch (e) {
                error.Closed => null,
                else => return e,
            };
            break :blk got orelse {
                std.debug.print("mux d {s}: quic://{s} took the request and did not answer\n", .{ verb, q.host_port });
                return 1;
            };
        },
    };
    defer frame.deinit(alloc);
    try proto.writeAllFd(std.posix.STDOUT_FILENO, frame.payload);
    try proto.writeAllFd(std.posix.STDOUT_FILENO, "\n");
    return 0;
}

fn dump(alloc: std.mem.Allocator, target: AdminTarget, vt_mode: bool, session: []const u8) !u8 {
    // vt byte ++ session-name tail, built by the wire module — empty is the
    // wire's own default spelling, so a bare `mux d dump` sends exactly the
    // one-byte payload that predates session names.
    var buf: [proto.debug_dump_max_len]u8 = undefined;
    const payload = proto.encodeDebugDumpNamed(&buf, vt_mode, session);
    return oneShotQuery(alloc, target, "dump", .debug_dump, payload, .dump_reply);
}

fn stats(alloc: std.mem.Allocator, target: AdminTarget) !u8 {
    return oneShotQuery(alloc, target, "stats", .stats_req, "", .stats_reply);
}

/// Request daemon shutdown, then wait for the peer process to exit rather than
/// only for socket unlink. Both an already-absent daemon and a completed stop
/// return zero, making the command idempotent for scripts.
///
/// Over QUIC there is no pid to wait on and no errno that says "absent", so
/// the verdict is the daemon's own goodbye: `Server.deinit` tears every
/// client slot down before it frees anything else, and a QUIC slot's
/// teardown is a CONNECTION_CLOSE to us. A port that never answered is a
/// refusal (rc 1), not a no-op — the silence may be a firewall or the wrong
/// key, and a script that read it as "already stopped" would be lied to.
/// The goodbye is read off `quic.Client.dead`, which a lost packet stream
/// or an idle expiry also sets, so the message says what was seen — the
/// connection ended after the request was delivered — and does not claim
/// the pid is gone; `mux d stats --quic` afterwards is the check.
fn stopCmd(alloc: std.mem.Allocator, target: AdminTarget) !u8 {
    const sock_path = switch (target) {
        .sock => |p| p,
        .quic => |q| {
            const deadline = adminDeadline();
            var l = openAdminQuic(alloc, "stop", q, deadline) orelse return 1;
            defer l.close();
            l.sendFrame(.stop_req, "") catch |e| {
                std.debug.print("mux d stop: quic: could not deliver the stop request: {s}\n", .{@errorName(e)});
                return 1;
            };
            // Nothing is ever wanted: the loop ends on the daemon's close.
            // `stop_req` as the wanted type is one the daemon never sends.
            const got = l.awaitFrame(alloc, .stop_req, msLeft(deadline), .{}) catch |e| switch (e) {
                error.Closed => {
                    std.debug.print(
                        "mux d: stopped (quic://{s} ended the connection after taking the request)\n",
                        .{q.host_port},
                    );
                    return 0;
                },
                else => return e,
            };
            if (got) |f| f.deinit(alloc);
            std.debug.print(
                "mux d stop: quic://{s} took the request and is still connected after {d}s\n",
                .{ q.host_port, admin_quic_deadline_ms / 1000 },
            );
            return 1;
        },
    };
    const stream = dial.dial(sock_path) catch {
        std.debug.print("mux d stop: nothing listening on {s}\n", .{sock_path});
        return 0;
    };
    // If the daemon exits between connect and write, the requested final state
    // is already reached. Track whether the frame was delivered so later
    // diagnostics do not point to logs for a request the daemon never received.
    const asked = if (proto.writeFrame(stream.handle, .stop_req, "")) |_| true else |_| false;
    const peer = peerPid(stream.handle);
    stream.close();

    // The pid is the verdict whenever the kernel names one. The path is
    // not: a daemon that lost its path to a successor watches that path
    // and re-binds it within a second of the successor's unlink
    // (`Server.watchSockPath`), so "the socket still answers" can mean the
    // OTHER daemon answered — and a stop that graded the path reported a
    // clean exit as a failure. Process exit is also what a supervisor
    // wants: the unlink happens before shell reaping and directory
    // cleanup, so a stop that returned on the unlink returned early.
    if (peer) |pid| return waitPidGone(alloc, pid, sock_path, asked);

    // No pid (a kernel that does not expose the peer): the socket's
    // silence is all there is. Probe before checking the deadline so the
    // final interval is observed; a wedged event loop can still accept
    // through the backlog, so silence here means the unlink ran.
    const stop_deadline_ms: i64 = 2000;
    const t0 = std.time.milliTimestamp();
    while (true) {
        if (!sockpath.answers(sock_path)) {
            std.debug.print("mux d: stopped\n", .{});
            return 0;
        }
        if (std.time.milliTimestamp() - t0 >= stop_deadline_ms) break;
        std.Thread.sleep(50 * std.time.ns_per_ms);
    }
    return stillRunning(alloc, sock_path, asked, @divTrunc(stop_deadline_ms, 1000));
}

/// The peer's pid, or null when the kernel cannot expose it; callers then
/// rely on socket shutdown.
fn peerPid(fd: std.posix.socket_t) ?std.posix.pid_t {
    const cred = server_os.peerCred(fd) orelse return null;
    return cred.pid;
}

/// Wait for the daemon's process to leave. Only a live process answers
/// signal 0 with success: ESRCH is the answer wanted, and EPERM means the
/// pid was reused by someone else's process, so the daemon is just as gone.
fn waitPidGone(alloc: std.mem.Allocator, pid: std.posix.pid_t, sock_path: []const u8, asked: bool) u8 {
    const gone_deadline_ms: i64 = 5000;
    const t0 = std.time.milliTimestamp();
    while (std.posix.kill(pid, 0)) |_| {
        if (std.time.milliTimestamp() - t0 >= gone_deadline_ms) {
            return stillRunning(alloc, sock_path, asked, @divTrunc(gone_deadline_ms, 1000));
        }
        std.Thread.sleep(20 * std.time.ns_per_ms);
    } else |_| {}
    std.debug.print("mux d: stopped\n", .{});
    return 0;
}

/// The stop's failure line. Whether the request was delivered decides
/// whether the log is worth pointing at: a daemon that never read the
/// frame has nothing to say about it there.
fn stillRunning(alloc: std.mem.Allocator, sock_path: []const u8, asked: bool, secs: i64) u8 {
    if (!asked) {
        std.debug.print(
            "mux d stop: could not deliver the stop request to {s}, and it is still running after {d}s\n",
            .{ sock_path, secs },
        );
        return 1;
    }
    var hint: [log_hint_len]u8 = undefined;
    std.debug.print(
        "mux d stop: the daemon on {s} is still running after {d}s{s}\n",
        .{ sock_path, secs, logHint(alloc, &hint) },
    );
    return 1;
}

/// What the remote-upgrade preflight learned. One ssh run answers everything:
/// `uname -m && command -v mux && mux d endpoint` — the bare endpoint verb,
/// which never starts a daemon — and the LINE COUNT is the diagnosis, because
/// `&&` stops at the first answerless step.
/// What the push's exit says about the candidate, by VALUE: the push word's
/// failure arm relays the remote word's true exit code, and the shell's
/// conventions make two of them diagnoses. 132 is 128+SIGILL — the candidate
/// EXECUTED and died on an instruction the remote CPU lacks, which for an
/// arch-checked ELF means a feature-level mismatch (a native build pushed at
/// a weaker x86_64). 126/127 are the shell saying it could not run the
/// candidate at all — a dynamic loader or libc that box does not have.
/// Everything else is the push not landing for reasons the remote's own
/// stderr (inherited) already narrated.
const PushVerdict = enum { landed, cpu_mismatch, cannot_load, failed };

fn pushVerdict(streamed: bool, term: std.process.Child.Term) PushVerdict {
    if (!streamed) return .failed;
    if (term != .Exited) return .failed;
    return switch (term.Exited) {
        0 => .landed,
        132 => .cpu_mismatch,
        126, 127 => .cannot_load,
        else => .failed,
    };
}

const Preflight = union(enum) {
    /// The ssh ran but stdout was empty: no shell spoke, nothing to trust.
    no_answer,
    /// `command -v mux` found nothing. Nothing to overwrite; inventing an
    /// install location for someone else's box is not this verb's call.
    no_mux,
    /// The remote's `uname -m`, verbatim, for a refusal line that shows both
    /// spellings — we stream THIS machine's image, so the words must match.
    bad_arch: []const u8,
    /// mux is installed at `path`; `daemon_up` says whether the endpoint verb
    /// answered, i.e. whether there is a daemon to trigger after the push.
    ready: struct { path: []const u8, daemon_up: bool },
};

/// `mux d upgrade HOST`: put THIS image on `host` and upgrade the daemon
/// there — the remote spelling of the local verb, in the same three moves a
/// hand upgrade makes. Preflight (one ssh, read), push (stream the image
/// into an atomic rename over the installed mux), trigger (run the freshly
/// installed binary as `d upgrade`, which makes IT the candidate and leaves
/// the version rule, the manifest and the serving check to the machinery
/// that already owns them). All three sshs run in the foreground with the
/// user's own tty, like the entry dial: this is a dial the user asked for,
/// and ssh's prompts are part of it.
fn remoteUpgradeCmd(alloc: std.mem.Allocator, host: []const u8, allow_same: bool) !u8 {
    // A refused write on a dead ssh must come back as BrokenPipe from the
    // push loop below, not as SIGPIPE ending this process mid-report.
    proxy.ignoreSigpipe();

    const pf_argv = try handoff.upgradePreflightArgv(alloc, host);
    defer handoff.freeArgv(alloc, pf_argv);
    var pf = std.process.Child.init(pf_argv, alloc);
    // stdin is EOF up front: a daemon's `endpoint` answer turns into a
    // stdio proxy that pumps until its stdin closes, and the preflight only
    // wants the announce line, not the pump.
    pf.stdin_behavior = .Ignore;
    pf.stdout_behavior = .Pipe;
    // ssh's prompts and complaints go straight to the user; there is no
    // wall on screen to protect during a `d` verb.
    pf.stderr_behavior = .Inherit;
    try pf.spawn();
    // Drain to EOF even past the cap — a child blocked on a full pipe never
    // exits, and wait() below would hold this process with it.
    var pf_buf: [4096]u8 = undefined;
    var pf_len: usize = 0;
    while (true) {
        var chunk: [1024]u8 = undefined;
        const n = std.posix.read(pf.stdout.?.handle, &chunk) catch break;
        if (n == 0) break;
        const keep = @min(n, pf_buf.len - pf_len);
        @memcpy(pf_buf[pf_len..][0..keep], chunk[0..keep]);
        pf_len += keep;
    }
    const pf_term = try pf.wait();
    // 255 is ssh's own exit code, distinct from any remote command's: the
    // dial itself failed, and ssh has already said why on stderr.
    if (pf_term == .Exited and pf_term.Exited == 255) {
        std.debug.print("mux d upgrade: ssh to {s} failed\n", .{host});
        return 1;
    }

    const local_arch = @tagName(builtin.cpu.arch);
    const ready = switch (parsePreflight(pf_buf[0..pf_len], local_arch)) {
        .no_answer => {
            std.debug.print(
                "mux d upgrade: {s}'s preflight answered nothing; not a box this can read\n",
                .{host},
            );
            return 1;
        },
        .no_mux => {
            std.debug.print(
                "mux d upgrade: no mux on {s}'s PATH; install one there first — " ++
                    "a push only replaces an install, it does not invent one\n",
                .{host},
            );
            return 1;
        },
        .bad_arch => |arch| {
            std.debug.print(
                "mux d upgrade: {s} is {s} and this image is {s}; refusing to push a binary that cannot run there\n",
                .{ host, arch, local_arch },
            );
            return 1;
        },
        .ready => |r| r,
    };

    const push_argv = handoff.upgradePushArgv(alloc, host, ready.path) catch |e| switch (e) {
        error.BadTargetPath => {
            std.debug.print(
                "mux d upgrade: {s} answered a mux path with a quote in it ({s}); not a box this will shell at\n",
                .{ host, ready.path },
            );
            return 1;
        },
        error.OutOfMemory => return error.OutOfMemory,
    };
    defer handoff.freeArgv(alloc, push_argv);
    var push = std.process.Child.init(push_argv, alloc);
    push.stdin_behavior = .Pipe;
    push.stdout_behavior = .Inherit;
    push.stderr_behavior = .Inherit;
    try push.spawn();
    // The running image rather than a saved path: "push this binary" can
    // only honestly mean the bytes executing here. How close the ask lands
    // depends on the OS. On Linux `openSelfExe` opens the running INODE, so
    // the bytes streamed are what is executing even after the file it was
    // started from has been renamed over. On an OS that can only open by
    // path, it streams whatever that path holds now — the same file in the
    // ordinary case, and a replacement's bytes if something swapped the
    // binary mid-push.
    const streamed: bool = blk: {
        var img = std.fs.openSelfExe(.{}) catch break :blk false;
        defer img.close();
        var buf: [64 * 1024]u8 = undefined;
        while (true) {
            const n = std.posix.read(img.handle, &buf) catch break :blk false;
            if (n == 0) break :blk true;
            var off: usize = 0;
            while (off < n) {
                off += std.posix.write(push.stdin.?.handle, buf[off..n]) catch break :blk false;
            }
        }
    };
    push.stdin.?.close();
    push.stdin = null;
    const push_term = try push.wait();
    switch (pushVerdict(streamed, push_term)) {
        .landed => {},
        .cpu_mismatch => {
            std.debug.print(
                "mux d upgrade: {s}'s CPU cannot run this image — the candidate died on an " ++
                    "illegal instruction, so the arch match was not a feature match; nothing " ++
                    "was replaced. push a static release build instead\n",
                .{host},
            );
            return 1;
        },
        .cannot_load => {
            std.debug.print(
                "mux d upgrade: {s} cannot load this image (no loader or libc for it there); " ++
                    "nothing was replaced\n",
                .{host},
            );
            return 1;
        },
        .failed => {
            std.debug.print("mux d upgrade: the push to {s} did not land; nothing was replaced\n", .{host});
            return 1;
        },
    }

    if (!ready.daemon_up) {
        std.debug.print(
            "mux d upgrade: installed {s} at {s}:{s}; no daemon running there to upgrade\n",
            .{ build_options.version, host, ready.path },
        );
        return 0;
    }

    const trig_argv = try handoff.upgradeTriggerArgv(alloc, host, allow_same);
    defer handoff.freeArgv(alloc, trig_argv);
    var trig = std.process.Child.init(trig_argv, alloc);
    // Fully inherited: the remote `mux d upgrade` reports in its own words —
    // the daemon's refusal reasons included — and those words are the report.
    trig.stdin_behavior = .Inherit;
    trig.stdout_behavior = .Inherit;
    trig.stderr_behavior = .Inherit;
    try trig.spawn();
    const trig_term = try trig.wait();
    return switch (trig_term) {
        .Exited => |code| code,
        else => 1,
    };
}

/// Slices into `out` — the caller keeps the buffer alive as long as the result.
/// Whether the box's `uname -m` word names the machine `want_arch` does.
///
/// The two sides spell one machine differently and neither is wrong: `uname -m`
/// on macOS prints `arm64`, and `want_arch` is `@tagName(builtin.cpu.arch)`,
/// which zig spells `aarch64` and has no `arm64` tag at all. Compared as bytes,
/// an Apple-silicon image refuses to push to an Apple-silicon box — the one
/// pairing where the check is certainly WRONG, since it is the same hardware on
/// both ends. `x86_64` is already the same word on every OS this runs on.
///
/// One direction only, and one pair only: `aarch64` is never what a kernel
/// prints here, so accepting it in the other direction would widen the map for
/// nothing, and every other mismatch is a real refusal.
fn archMatches(reported: []const u8, want_arch: []const u8) bool {
    if (std.mem.eql(u8, reported, want_arch)) return true;
    return std.mem.eql(u8, reported, "arm64") and std.mem.eql(u8, want_arch, "aarch64");
}

fn parsePreflight(out: []const u8, want_arch: []const u8) Preflight {
    var lines = std.mem.splitScalar(u8, out, '\n');
    const arch = lines.next() orelse "";
    if (arch.len == 0) return .no_answer;
    // The REPORTED word is what comes back on a refusal, so the message names
    // the box's own spelling and the image's own spelling side by side.
    if (!archMatches(arch, want_arch)) return .{ .bad_arch = arch };
    const path = lines.next() orelse "";
    if (path.len == 0) return .no_mux;
    const announce = lines.next() orelse "";
    return .{ .ready = .{ .path = path, .daemon_up = announce.len != 0 } };
}

/// Ask the daemon on `sock_path` to replace itself with the current binary. The
/// candidate supplies its version and path; the running daemon decides whether
/// the upgrade is allowed.
fn upgradeCmd(alloc: std.mem.Allocator, sock_path: []const u8, allow_same: bool) !u8 {
    var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
    const exe = std.fs.selfExePath(&exe_buf) catch {
        std.debug.print("mux d upgrade: cannot find own image\n", .{});
        return 1;
    };

    var buf: [std.fs.max_path_bytes + 64]u8 = undefined;
    const payload = proto.encodeUpgradeReq(&buf, .{
        .allow_same_version = allow_same,
        .version = build_options.version,
        .path = exe,
    }) catch {
        std.debug.print("mux d upgrade: cannot name {s} in a request\n", .{exe});
        return 1;
    };
    // Bounded, because a daemon older than this feature drops an unknown
    // frame without a word: the expiry is a diagnosis, not a timeout. So is
    // EOF — an older daemon that drops the connection over a frame it
    // cannot read reaches the same conclusion silence does.
    const reply = dial.ask(alloc, sock_path, .upgrade_req, payload, .upgrade_reply, 5000) catch |e| ask: {
        switch (e) {
            error.NoDaemon => {
                std.debug.print("mux d upgrade: nothing listening on {s}\n", .{sock_path});
                return 1;
            },
            error.RequestNotSent => {
                std.debug.print("mux d upgrade: {s} closed before the request landed\n", .{sock_path});
                return 1;
            },
            // Treat an unreadable reply like timeout or EOF: no usable upgrade
            // response was received.
            else => break :ask null,
        }
    };
    // A frame with no status byte is not an answer — `parseUpgradeReply` says
    // so — and joins timeout and EOF at the "no reply" line below.
    if (reply) |frame| skip: {
        defer frame.deinit(alloc);
        const answer = proto.parseUpgradeReply(frame.payload) orelse break :skip;
        if (!answer.ok) {
            // The daemon's words, verbatim: it is the side that knows which
            // check failed, and paraphrasing here would lose the versions.
            std.debug.print("mux d upgrade: refused: {s}\n", .{answer.reason});
            // Add compatibility context when version probing fails: v0.0.1-15
            // and older expect `muxd <version>`, while this binary reports
            // `mux <version>`.
            if (std.mem.eql(u8, answer.reason, "version: output mismatch"))
                std.debug.print(
                    "mux d upgrade: if that daemon is v0.0.1-15 or older, it wants a " ++
                        "candidate that prints `muxd <version>` and this one is `mux`. " ++
                        "There is no in-place path across that rename: `mux d stop` then " ++
                        "`mux d start -d`, once.\n",
                    .{},
                );
            return 1;
        }
        std.debug.print("mux d: upgraded to {s}\n", .{build_options.version});
        return confirmServing(alloc, sock_path);
    }
    std.debug.print(
        "mux d upgrade: no reply: this daemon predates upgrade — stop and run\n",
        .{},
    );
    return 1;
}

/// Verify that the upgraded daemon, rather than merely the inherited listening
/// socket, is serving protocol requests.
fn confirmServing(alloc: std.mem.Allocator, sock_path: []const u8) u8 {
    // A connect is insufficient because the listener descriptor survives exec
    // and can queue connections throughout handover. A valid reply proves that
    // the new image is accepting and processing frames.
    const deadline_ms: u32 = 5000;
    if (dial.ask(alloc, sock_path, .stats_req, "", .stats_reply, deadline_ms) catch |e| switch (e) {
        error.NoDaemon => {
            std.debug.print("mux d upgrade: {s} stopped answering after the exec\n", .{sock_path});
            return 1;
        },
        else => return 1,
    }) |frame| {
        frame.deinit(alloc);
        return 0;
    }
    const secs = deadline_ms / 1000;
    var hint: [log_hint_len]u8 = undefined;
    std.debug.print(
        "mux d upgrade: exec'd, but {s} has not answered in {d}s{s}\n",
        .{ sock_path, secs, logHint(alloc, &hint) },
    );
    return 1;
}

const log_hint_len = std.fs.max_path_bytes + 64;

/// The "where the rest of the story is" clause, or "" when there is no path to
/// name — these lines are read by someone who is not at that box. Only when
/// the path resolves, so an absent HOME does not replace the finding with a
/// trace; and the hedge stays in the words, since a foreground start logs to stderr.
fn logHint(alloc: std.mem.Allocator, buf: []u8) []const u8 {
    const log = xdg.logPath(alloc) catch return "";
    defer alloc.free(log);
    // A path too long for the buffer is dropped rather than clipped: half
    // a path is worse than none, and `log_hint_len` clears PATH_MAX, so
    // the only paths it drops are ones nothing could have opened anyway.
    return std.fmt.bufPrint(
        buf,
        " (if it was started detached, its log is {s})",
        .{log},
    ) catch "";
}

/// Run `mux d proxy` after emitting one endpoint announcement line. Direct
/// attaches use `--start` to ensure a daemon exists; background wall polls omit
/// it and remain read-only.
///
/// The announcement is mandatory because the client blocks until it reads one
/// newline-terminated line. Silence would hang the SSH attachment.
///
/// Recoverable QUIC failures announce `endpoint none` and continue over SSH.
/// Failures that also prevent proxying exit and let the client observe EOF;
/// they must not announce a working fallback first.
///
/// Stdout contains only the announcement and protocol frames. Diagnostics go to
/// stderr, which SSH forwards separately.
fn endpointCmd(alloc: std.mem.Allocator, sock_path: []const u8, out_fd: std.posix.fd_t, start: bool) !u8 {
    // `--start` ensures a daemon before querying it. Forward the same explicit
    // socket path so startup cannot bind the default socket while this command
    // probes another path.
    //
    // A failed spawn has already said so on stderr, so returning here leaves
    // the announce UNWRITTEN — the "no daemon" shape the client reads.
    if (start) {
        const sock_z = try alloc.dupeZ(u8, sock_path);
        defer alloc.free(sock_z);
        const run_args = [_][:0]const u8{ "--sock", sock_z };
        if (startDetached(alloc, &run_args, sock_path, "mux d endpoint") == null) return 1;
    }

    // Silent otherwise: the wall runs this over ssh once a second per host, so
    // a word on stderr here is a word on the wall's alternate screen.
    if (!sockpath.answers(sock_path)) return 1;

    // The announcement and proxy share stdout. Install the proxy's SIGPIPE
    // handling before either writes so a closed pipe returns EPIPE instead of
    // terminating the process.
    proxy.ignoreSigpipe();

    // Resolve or create the key before asking for the endpoint. The daemon's
    // lazy QUIC bind uses the default path only when the file already exists.
    const key = announceKey(alloc);
    const port: u16 = if (key == null) 0 else askEndpointPort(alloc, sock_path);

    var line_buf: [handoff.announce_max_len]u8 = undefined;
    const line: []const u8 = blk: {
        const k = key orelse break :blk handoff.announce_none;
        // Endpoint reply zero means no listener. Convert it to the explicit
        // negative announcement before formatting, which intentionally rejects
        // port zero.
        if (port == 0) {
            reportNoListener(alloc, sock_path);
            break :blk handoff.announce_none;
        }
        // Fall back to SSH instead of panicking if announcement formatting ever
        // fails; losing QUIC is safer than terminating the pending session.
        break :blk handoff.formatAnnounce(&line_buf, .{ .port = port, .key = k.bytes }) catch
            handoff.announce_none;
    };
    proto.writeAllFd(out_fd, line) catch |err| {
        // The proxy also requires stdout, so a failed announcement leaves no
        // usable fallback stream. Report the actual write error.
        std.debug.print(
            "mux d endpoint: cannot write the announce to stdout: {s}\n",
            .{@errorName(err)},
        );
        return 1;
    };

    return proxy.run(sock_path);
}

/// Report that the daemon is reachable but has no QUIC listener. The detailed
/// reason is in the remote daemon log, so include its path when available.
fn reportNoListener(alloc: std.mem.Allocator, sock_path: []const u8) void {
    var hint: [log_hint_len]u8 = undefined;
    std.debug.print(
        "mux d endpoint: the daemon on {s} produced no QUIC listener; staying on ssh{s}\n",
        .{ sock_path, logHint(alloc, &hint) },
    );
}

/// Resolve the key announced by `mux d endpoint`. Return null after printing one
/// diagnostic when no usable key is available.
fn announceKey(alloc: std.mem.Allocator) ?quic.Key {
    // Resolve the default path up front so a missing HOME can be distinguished
    // from failures involving an actual path.
    const dflt: ?[]const u8 = xdg.keyPath(alloc) catch null;
    defer if (dflt) |p| alloc.free(p);

    switch (announceKeyFrom(xdg.pickKey(null, std.posix.getenv(xdg.key_env)), dflt)) {
        .key => |k| return k,
        .no_path => std.debug.print(
            "mux d endpoint: no HOME to resolve a key path; staying on ssh\n",
            .{},
        ),
        .create_failed => |f| std.debug.print(
            "mux d endpoint: cannot create {s}: {s}; staying on ssh\n",
            .{ f.path, @errorName(f.err) },
        ),
        .load_failed => |f| reportKeyRefusal(f.path, f.err),
    }
    return null;
}

/// Result of selecting, creating, and loading the endpoint key. Keeping this
/// decision separate from diagnostics makes precedence and failures testable.
const KeyResolution = union(enum) {
    key: quic.Key,
    /// Neither `MUX_KEY_FILE` nor a HOME-based default path is available.
    no_path,
    /// The default key was absent and could not be created. Kept apart
    /// from `load_failed` because it is the cause and the load's
    /// `KeyFileMissing` would only be its symptom.
    create_failed: struct { path: []const u8, err: anyerror },
    load_failed: struct { path: []const u8, err: anyerror },
};

/// Load `MUX_KEY_FILE` when supplied, otherwise load or create the default key.
/// This matches daemon lazy-bind precedence so the announced key authenticates
/// against the listener that daemon creates.
///
/// Only the default is created when absent. An explicit environment path is
/// user-managed and must already exist. Inputs are injected and this function
/// prints nothing so each branch can be tested directly.
fn announceKeyFrom(env: ?[]const u8, dflt: ?[]const u8) KeyResolution {
    if (env) |p| return if (quic.Key.load(p)) |k|
        .{ .key = k }
    else |err|
        .{ .load_failed = .{ .path = p, .err = err } };

    const path = dflt orelse return .no_path;
    // Ignore `KeyExists` and load the existing key. Preserve other creation
    // failures because they identify causes such as an unwritable directory
    // more accurately than the subsequent missing-file load error.
    const create_failed: ?anyerror = if (xdg.writeNewKey(path)) |_|
        null
    else |err| if (err == error.KeyExists) null else err;

    return if (quic.Key.load(path)) |k|
        .{ .key = k }
    else |load_err| if (create_failed) |err|
        .{ .create_failed = .{ .path = path, .err = err } }
    else
        .{ .load_failed = .{ .path = path, .err = load_err } };
}

/// Print a key-loading diagnostic suitable for forwarding over SSH. Reuse
/// `quic.keyRefusalBody` so every CLI path reports the same reason.
fn reportKeyRefusal(path: []const u8, err: anyerror) void {
    var buf: [quic.key_refusal_len]u8 = undefined;
    std.debug.print(
        "mux d endpoint: {s}; staying on ssh\n",
        .{quic.keyRefusalBody(&buf, err, path)},
    );
}

/// Request the daemon's QUIC port with a bounded wait. Return zero for any
/// failure so old daemons that ignore the request fall back to SSH instead of
/// hanging.
fn askEndpointPort(alloc: std.mem.Allocator, sock_path: []const u8) u16 {
    const frame = (dial.ask(
        alloc,
        sock_path,
        .endpoint_req,
        "",
        .endpoint_reply,
        start_deadline_ms,
    ) catch null) orelse return 0;
    defer frame.deinit(alloc);
    return proto.decodeEndpointReply(frame.payload) catch 0;
}

/// Run `mux d start` in the current process or spawn it for `-d`. Forward every
/// argument after `start` unchanged so foreground and detached parsing match.
fn startCmd(alloc: std.mem.Allocator, o: DaemonArguments, sock_path: []const u8, forwarded: []const [:0]const u8) !u8 {
    if (!o._detach) return run(alloc, o, sock_path);
    // Remove `-d` from child argv to prevent recursive detached spawning.
    const r = startDetached(alloc, forwarded[1..], sock_path, "mux d") orelse return 1;
    if (r == .already_running) {
        std.debug.print(
            "mux d: already running on {s} (stop it first with `mux d stop --sock {s}` if you meant different flags)\n",
            .{ sock_path, sock_path },
        );
    }
    return 0;
}

/// Time allowed for a spawned daemon to begin answering on its socket.
const start_deadline_ms: u32 = 2000;

const StartOutcome = enum { already_running, started };

const StartError = error{ SpawnFailed, NeverAnswered };

/// Destination and formatting policy for detached-start progress. Callers set
/// the command prefix and whether a TTY receives animated dots. An already
/// running daemon produces no progress output.
const StartProgress = struct {
    fd: std.posix.fd_t,
    prefix: []const u8,
    tty: bool,

    fn emit(self: StartProgress, s: []const u8) void {
        _ = std.posix.write(self.fd, s) catch {};
    }

    fn emitFmt(self: StartProgress, comptime fmt: []const u8, args: anytype) void {
        var buf: [256]u8 = undefined;
        const s = std.fmt.bufPrint(&buf, fmt, args) catch return;
        self.emit(s);
    }
};

/// Test-only pid of the most recent spawn, used to reap deliberately persistent
/// stub processes. Callers are single-threaded, so synchronization is omitted.
var last_spawned_pid: std.posix.pid_t = 0;

/// Implement detached startup: probe the socket, fork, exec this binary, and
/// poll until the daemon answers or the deadline expires.
fn forkDaemon(
    alloc: std.mem.Allocator,
    exe_path: []const u8,
    run_args: []const [:0]const u8,
    sock_path: []const u8,
    progress: StartProgress,
    deadline_ms: u32,
    log_path_override: ?[]const u8,
) StartError!StartOutcome {
    // `NeverAnswered` leaves the child running because it may finish startup
    // after the caller's deadline and be available on retry.
    if (sockpath.answers(sock_path)) return .already_running;

    std.posix.access(exe_path, std.posix.X_OK) catch return error.SpawnFailed;

    // Owned either way, so one `free` covers both: a caller-supplied path is
    // duped rather than borrowed, because the xdg branch must allocate.
    const log_path = if (log_path_override) |p|
        alloc.dupe(u8, p) catch return error.SpawnFailed
    else
        xdg.logPath(alloc) catch return error.SpawnFailed;
    defer alloc.free(log_path);
    if (std.fs.path.dirname(log_path)) |dir|
        std.fs.cwd().makePath(dir) catch return error.SpawnFailed;
    // Always open with O_APPEND. One XDG log serves every daemon socket on the
    // machine, so truncation or writing from offset zero could destroy another
    // daemon's log.
    const log: std.fs.File = .{
        .handle = std.posix.open(log_path, .{
            .ACCMODE = .WRONLY,
            .CREAT = true,
            .APPEND = true,
            // Set CLOEXEC explicitly because the raw `posix.open` API does not.
            // The descriptor must not propagate through the daemon into session
            // shells; dup2 intentionally clears CLOEXEC for stdout and stderr.
            .CLOEXEC = true,
        }, 0o600) catch return error.SpawnFailed,
    };
    defer log.close();
    const devnull = std.fs.cwd().openFile("/dev/null", .{}) catch return error.SpawnFailed;
    defer devnull.close();

    // Build null-terminated child argv as `mux d start <forwarded...>`. Keeping
    // the mode word makes the long-lived daemon identifiable in process lists.
    const exe_z = alloc.dupeZ(u8, exe_path) catch return error.SpawnFailed;
    defer alloc.free(exe_z);
    const argv = alloc.allocSentinel(?[*:0]const u8, run_args.len + 3, null) catch
        return error.SpawnFailed;
    defer alloc.free(argv);
    argv[0] = "mux";
    argv[1] = "d";
    argv[2] = "start";
    for (run_args, 0..) |a, i| argv[i + 3] = a.ptr;

    progress.emitFmt("{s}: starting\u{2026}", .{progress.prefix});
    if (!progress.tty) progress.emit("\n");

    const t0 = std.time.milliTimestamp();
    // The fork itself is `server_os.forkDetached`; this function owns what
    // goes INTO it — the log, the argv and the deadline — and folder rule 6
    // names that file as the one fork.
    const pid = server_os.forkDetached(exe_z.ptr, argv.ptr, devnull.handle, log.handle) catch {
        if (progress.tty) progress.emit("\n");
        return error.SpawnFailed;
    };
    last_spawned_pid = pid;

    // Parent: poll the socket. Animate dots only on a TTY so scripted output is
    // stable.
    var next_dot: i64 = t0 + 250;
    // A pid owes us exactly one reap: a second `waitpid` gets ECHILD, which
    // `std.posix.waitpid` answers with `unreachable`. That panic would land
    // precisely on the path that exists to report a child that died young.
    var reaped = false;
    while (true) {
        if (sockpath.answers(sock_path)) {
            const secs = @as(f64, @floatFromInt(std.time.milliTimestamp() - t0)) / 1000.0;
            // The leading space exists to follow the dots on a tty. There
            // are no dots on a non-tty — the ssh proxy's case, and now the
            // common one — where it would only be a stray space at the
            // start of a scripted line.
            if (progress.tty) progress.emit(" ");
            progress.emitFmt("up ({d:.1}s) pid={d}\n", .{ secs, pid });
            return .started;
        }
        const now = std.time.milliTimestamp();
        if (now - t0 >= deadline_ms) {
            // The newline terminates the dot line, so it belongs to the
            // same condition the dots do: on a non-tty there are no dots
            // and it would only put a blank line into scripted output.
            if (progress.tty) progress.emit("\n");
            // "daemon" in the body, because the prefix is already the
            // program speaking: `mux d: daemon did not answer` reads
            // correctly and would not survive naming the binary twice.
            progress.emitFmt(
                "{s}: daemon did not answer within {d}s \u{2014} log: {s}\n",
                .{ progress.prefix, deadline_ms / 1000, log_path },
            );
            return error.NeverAnswered;
        }
        if (progress.tty and now >= next_dot) {
            progress.emit(".");
            next_dot = now + 250;
        }
        // Reap an exited child once, including a start-race loser or a child
        // with invalid flags. Repeated waitpid calls after reaping would fail.
        if (!reaped and std.posix.waitpid(pid, std.posix.W.NOHANG).pid == pid)
            reaped = true;
        std.Thread.sleep(50 * std.time.ns_per_ms);
    }
}

/// Start a detached daemon for either `-d` or `endpoint --start` using the
/// current executable and shared deadline. Return null after reporting failure.
fn startDetached(alloc: std.mem.Allocator, run_args: []const [:0]const u8, sock_path: []const u8, prefix: []const u8) ?StartOutcome {
    var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
    const exe = spawn.selfExe(&exe_buf);
    const progress: StartProgress = .{
        .fd = std.posix.STDERR_FILENO,
        .prefix = prefix,
        .tty = std.posix.isatty(std.posix.STDERR_FILENO),
    };
    return forkDaemon(alloc, exe, run_args, sock_path, progress, start_deadline_ms, null) catch |err| {
        // `NeverAnswered`'s failure line was already printed by StartProgress. A
        // spawn that never happened has nothing printed yet, and
        // `std.debug.print` rather than `emitFmt` because an exe path can run to
        // `max_path_bytes` and a fixed buffer would drop the whole line.
        if (err == error.SpawnFailed)
            std.debug.print("{s}: could not spawn {s}: {s}\n", .{ prefix, exe, @errorName(err) });
        return null;
    };
}

fn keygen(alloc: std.mem.Allocator) !u8 {
    const path = try xdg.keyPath(alloc);
    defer alloc.free(path);
    xdg.writeNewKey(path) catch |err| switch (err) {
        error.KeyExists => {
            std.debug.print(
                "mux d keygen: {s} already exists; rotation is `rm` + `keygen`, deliberately\n",
                .{path},
            );
            return 1;
        },
        else => |e| return e,
    };
    var buf: [std.fs.max_path_bytes + 1]u8 = undefined;
    const line = std.fmt.bufPrint(&buf, "{s}\n", .{path}) catch unreachable;
    _ = std.posix.write(std.posix.STDOUT_FILENO, line) catch {};
    return 0;
}

// ---------------------------------------------------------------------------
// Tests. These run only because `exe_mod` is in build.zig's test loop: a test
// written here without it compiles and silently never executes.
// ---------------------------------------------------------------------------

/// The tests must speak argsAlloc's type: a slice of
/// sentinel-terminated strings.
fn parse(comptime argv: []const [:0]const u8) DaemonInvocation {
    return parseArgs(argv);
}

/// `specs` holds plain slices; a table-driven test needs argsAlloc's type.
fn nameZ(comptime name: []const u8) [:0]const u8 {
    const buf = name ++ "\x00";
    return buf[0..name.len :0];
}

test "parseArgs: subcommands and their existing flags" {
    const r = parse(&.{ "d", "start" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .start);
    try std.testing.expect(r.command.sock == null);
    try std.testing.expectEqual(@as(u16, 80), r.command.cols);
    try std.testing.expectEqual(@as(u16, 24), r.command.rows);

    const d = parse(&.{ "d", "dump", "--vt", "--sock", "/tmp/x.sock" });
    try std.testing.expect(d.command._cmd == .dump);
    try std.testing.expect(d.command.vt);
    try std.testing.expectEqualStrings("/tmp/x.sock", d.command.sock.?);
    // No --session named: nothing to validate, and `dump` spells the absence
    // on the wire as the empty tail.
    try std.testing.expect(d.command.session == null);

    const g = parse(&.{ "d", "start", "--cols", "120", "--rows", "40", "--shell", "/bin/dash" });
    try std.testing.expectEqual(@as(u16, 120), g.command.cols);
    try std.testing.expectEqual(@as(u16, 40), g.command.rows);
    try std.testing.expectEqualStrings("/bin/dash", g.command.shell.?);

    try std.testing.expect(parse(&.{"d"}).usage == .no_command);
    try std.testing.expect(parse(&.{ "d", "wat" }).usage == .unknown_command);
    try std.testing.expect(parse(&.{ "d", "start", "--wat" }).usage == .unknown_arg);
}

test "pushVerdict: the relayed exit code names the mismatch uname -m cannot see" {
    const T = std.process.Child.Term;
    // 0 with the stream complete: the rename happened.
    try std.testing.expect(pushVerdict(true, T{ .Exited = 0 }) == .landed);
    // 132 is the shell's 128+SIGILL: a right-arch ELF whose instructions the
    // remote CPU lacks — the native-build push that bricked a live box
    // (2026-09-01) and the case the preflight's arch check cannot catch.
    try std.testing.expect(pushVerdict(true, T{ .Exited = 132 }) == .cpu_mismatch);
    // 126 and 127 are the shell's own cannot-execute codes: the candidate is
    // on disk but nothing there can load it — a dynamic interpreter or libc
    // the box does not have.
    try std.testing.expect(pushVerdict(true, T{ .Exited = 126 }) == .cannot_load);
    try std.testing.expect(pushVerdict(true, T{ .Exited = 127 }) == .cannot_load);
    // Everything else is the push not landing: ssh's own 255, a full disk's
    // 1, a killed ssh — and a clean exit whose stream broke midway, which is
    // a lying remote rather than a landed push.
    try std.testing.expect(pushVerdict(true, T{ .Exited = 255 }) == .failed);
    try std.testing.expect(pushVerdict(true, T{ .Signal = 15 }) == .failed);
    try std.testing.expect(pushVerdict(false, T{ .Exited = 0 }) == .failed);
}

test "parsePreflight: the line count is the diagnosis" {
    // Three lines: arch, the installed path, an endpoint announce — a daemon
    // is up and the full push-then-upgrade flow applies.
    const up = parsePreflight("x86_64\n/home/u/.local/bin/mux\nquic 1.2.3.4:1 k\n", "x86_64");
    try std.testing.expectEqualStrings("/home/u/.local/bin/mux", up.ready.path);
    try std.testing.expect(up.ready.daemon_up);

    // Two lines: mux is installed but `mux d endpoint` answered nothing —
    // push the image, skip the trigger.
    const idle = parsePreflight("x86_64\n/usr/local/bin/mux\n", "x86_64");
    try std.testing.expectEqualStrings("/usr/local/bin/mux", idle.ready.path);
    try std.testing.expect(!idle.ready.daemon_up);

    // One line: `command -v mux` found nothing — there is nothing to
    // overwrite, and inventing an install location is not this verb's call.
    try std.testing.expect(parsePreflight("x86_64\n", "x86_64") == .no_mux);

    // The remote's word comes back for the refusal line, so the user reads
    // both spellings rather than a bare "mismatch".
    const arm = parsePreflight("aarch64\n/usr/bin/mux\n", "x86_64");
    try std.testing.expectEqualStrings("aarch64", arm.bad_arch);

    // Nothing at all is its own answer: the ssh ran but no shell spoke.
    try std.testing.expect(parsePreflight("", "x86_64") == .no_answer);
    try std.testing.expect(parsePreflight("\n", "x86_64") == .no_answer);
}

test "parsePreflight: a Mac's arm64 is the aarch64 this image is built for" {
    // The pairing this exists for: an Apple-silicon image pushing to an
    // Apple-silicon box. `uname -m` says `arm64`, `@tagName(builtin.cpu.arch)`
    // says `aarch64`, and a byte compare refused the one push that is
    // certainly safe. There is one Mac in the fixture and `mux d upgrade`
    // needs two, so `make xos` cannot reach this and the unit test is the pin.
    const mac = parsePreflight("arm64\n/Users/u/.local/bin/mux\nquic 1.2.3.4:1 k\n", "aarch64");
    try std.testing.expectEqualStrings("/Users/u/.local/bin/mux", mac.ready.path);
    try std.testing.expect(mac.ready.daemon_up);

    // Neither half of the map loosens a real mismatch, and both refusals still
    // carry the BOX's spelling — test/xos.sh's upgrade-refused leg reads both
    // of these sentences off the wire in the two directions it pushes.
    const mac_from_intel = parsePreflight("arm64\n/Users/u/.local/bin/mux\n", "x86_64");
    try std.testing.expectEqualStrings("arm64", mac_from_intel.bad_arch);
    const intel_from_mac = parsePreflight("x86_64\n/home/u/.local/bin/mux\n", "aarch64");
    try std.testing.expectEqualStrings("x86_64", intel_from_mac.bad_arch);

    // The map is one direction: a box that reports the zig tag is not a box
    // this has ever met, and it is not what makes an x86_64 image acceptable.
    try std.testing.expect(!archMatches("aarch64", "x86_64"));
    try std.testing.expect(archMatches("aarch64", "aarch64"));
}

test "parseArgs: upgrade takes one HOST word, and only upgrade does" {
    const r = parse(&.{ "d", "upgrade", "box" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .upgrade);
    try std.testing.expectEqualStrings("box", r.command._host.?);

    // The flag still rides beside the word, in either order.
    const f = parse(&.{ "d", "upgrade", "--allow-same-version", "box" });
    try std.testing.expectEqualStrings("box", f.command._host.?);
    try std.testing.expect(f.command.allow_same_version);

    // No word means the local daemon, exactly as before.
    try std.testing.expect(parse(&.{ "d", "upgrade" }).command._host == null);

    // A second word is a mistake, not a second host.
    try std.testing.expect(parse(&.{ "d", "upgrade", "box", "box2" }).usage == .unknown_arg);

    // Every other verb still refuses a bare word: a host on `stop` would be
    // a remote stop nobody designed.
    try std.testing.expect(parse(&.{ "d", "stop", "box" }).usage == .unknown_arg);

    // --sock names a LOCAL socket and HOST names another box; honoring one
    // and dropping the other silently would leave the user believing both.
    try std.testing.expect(parse(&.{ "d", "upgrade", "box", "--sock", "/x" }).usage == .sock_with_host);
}

test "parse: dump --session rides into the payload" {
    const d = parse(&.{ "d", "dump", "--session", "b", "--sock", "/tmp/x.sock" });
    try std.testing.expect(d == .command);
    try std.testing.expectEqualStrings("b", d.command.session.?.name);

    // Reject an unaddressable session name before encoding it. The parse result
    // identifies the flag whose value failed validation.
    const bad = parse(&.{ "d", "dump", "--session", "has space" });
    try std.testing.expect(bad.usage == .bad_value);
    try std.testing.expectEqualStrings("--session", bad.usage.bad_value);
}

test "parseArgs: --key without --quic is refused; --quic alone defers to main" {
    const both = parse(&.{ "d", "start", "--quic", "0.0.0.0:4433", "--key", "/k" });
    try std.testing.expect(both == .command);
    try std.testing.expectEqualStrings("0.0.0.0:4433", both.command.quic.?);
    try std.testing.expectEqualStrings("/k", both.command.key.?);

    // --quic without --key is no longer a parse error: main resolves
    // MUX_KEY_FILE and the default path, and parse cannot see either.
    const deferred = parse(&.{ "d", "start", "--quic", "0.0.0.0:4433" });
    try std.testing.expect(deferred == .command);
    try std.testing.expect(deferred.command.key == null);

    // A key with nowhere to listen is still a mistake with no reading that
    // makes it sensible, and parse can see the whole of it.
    try std.testing.expect(parse(&.{ "d", "start", "--key", "/k" }).usage == .key_without_quic);

    // Neither is the ordinary case and must stay silent.
    const neither = parse(&.{ "d", "start" });
    try std.testing.expect(neither.command.quic == null);
    try std.testing.expect(neither.command.key == null);
}

test "parseArgs: --quic-idle-ms defaults, parses, and rejects invalid values" {
    const dflt = parse(&.{ "d", "start", "--quic", "127.0.0.1:1", "--key", "/k" });
    // Compare with the documented literal so this test detects an accidental
    // change to the source default.
    try std.testing.expectEqual(@as(u32, 15_000), dflt.command.quic_idle_ms.ms);

    const set = parse(&.{ "d", "start", "--quic", "127.0.0.1:1", "--key", "/k", "--quic-idle-ms", "2500" });
    try std.testing.expectEqual(@as(u32, 2500), set.command.quic_idle_ms.ms);

    // ngtcp2 interprets zero as no timeout, which would invert the user's
    // request for an immediate timeout, so reject it.
    try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "0" }).usage == .bad_value);
    try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "soon" }).usage == .bad_value);
    try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "-5" }).usage == .bad_value);
    // Reject values wider than u32 before converting milliseconds to
    // nanoseconds.
    try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "99999999999" }).usage == .bad_value);
    // The idle flag alone does not turn QUIC on, and must not smuggle the
    // both-or-neither rule past the check.
    try std.testing.expect(parse(&.{ "d", "start", "--quic-idle-ms", "2500" }) == .command);

    // Same treatment for the numbers that were already here.
    try std.testing.expect(parse(&.{ "d", "start", "--cols", "wide" }).usage == .bad_value);
    try std.testing.expect(parse(&.{ "d", "start", "--rows", "99999" }).usage == .bad_value);
}

test "parseArgs: a value-taking flag at the end of argv names itself" {
    // This used to report "unknown argument: --quic", which blames the flag
    // rather than the missing value.
    inline for (.{ "--sock", "--shell", "--cols", "--rows", "--quic", "--key", "--quic-idle-ms", "--session", "--resume-fd", "--resume-fail-at" }) |flag| {
        const r = parse(&.{ "d", "start", flag });
        try std.testing.expect(r.usage == .missing_value);
        try std.testing.expectEqualStrings(flag, r.usage.missing_value);
    }
}

// The table cannot verify hand-written usage text by itself. Anchor each check
// to the command position so incidental mentions, such as "proxy" in another
// description, do not count as documentation.
test "usage names every subcommand" {
    inline for (specs) |s| {
        const named = std.mem.indexOf(u8, usage, "\n  mux d " ++ s.name) != null;
        // expect() alone would print only "expected true", which does not
        // say which verb went missing.
        if (!named) std.debug.print("usage never names the subcommand `{s}`\n", .{s.name});
        try std.testing.expect(named);
    }
}

test "parseBindAddr: a hostname is rejected rather than resolved" {
    const a = try parseBindAddr("127.0.0.1:4433");
    try std.testing.expectEqual(@as(u16, 4433), a.getPort());

    const six = try parseBindAddr("[::1]:4433");
    try std.testing.expectEqual(@as(u16, 4433), six.getPort());
    try std.testing.expect(six.any.family == std.posix.AF.INET6);

    // Bind configuration requires one numeric address, not a hostname that may
    // resolve to several addresses.
    try std.testing.expect(std.meta.isError(parseBindAddr("localhost:4433")));
}

test "keygen: a generated key loads through quic.Key.load" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();

    var buf: [128]u8 = undefined;
    const path = try std.fmt.bufPrint(&buf, "{s}/key", .{tmp.path()});
    try xdg.writeNewKey(path);
    _ = try quic.Key.load(path);
}

test "parseArgs: --version is a command, not a flag on one" {
    const r = parse(&.{ "d", "--version" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .version);

    // Version takes precedence when attached to another command. Test parsing
    // directly because `main` would write to the test runner's stdout channel.
    const on_start = parse(&.{ "d", "start", "--sock", "/x", "--version" });
    try std.testing.expect(on_start == .command);
    try std.testing.expect(on_start.command._cmd == .version);
}

test "parseArgs: --help is a command, and a flag on one, and both exit 0 on stdout" {
    const bare = parse(&.{ "d", "--help" });
    try std.testing.expect(bare == .command);
    try std.testing.expect(bare.command._cmd == .help);

    // On a subcommand, help comes from flag parsing and takes precedence over
    // adjacent syntax such as a missing `--sock` value.
    try std.testing.expect(parse(&.{ "d", "start", "--help" }).usage == .help);
    try std.testing.expect(parse(&.{ "d", "dump", "-h", "--sock", "/x" }).usage == .help);
    try std.testing.expect(parse(&.{ "d", "start", "--sock", "--help" }).usage == .help);

    // Use `usageCode` because `usageExit` writes to the test runner's stdout
    // protocol channel.
    try std.testing.expectEqual(@as(u8, 0), usageCode(.help));
    try std.testing.expectEqual(@as(u8, 2), usageCode(.no_command));
}

test "parseArgs: keygen takes no flags" {
    const r = parse(&.{ "d", "keygen" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .keygen);
    try std.testing.expect(parse(&.{ "d", "keygen", "--sock", "/x" }).usage == .unknown_arg);
}

test "parseArgs: -d is a word in `start`'s second slot, and everything after it is the daemon's" {
    const r = parse(&.{ "d", "start", "--sock", "/tmp/x.sock", "--cols", "100" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .start);
    try std.testing.expectEqualStrings("/tmp/x.sock", r.command.sock.?);
    try std.testing.expectEqual(@as(u16, 100), r.command.cols);
    // Foreground mode is the default so startup failures remain visible in the
    // invoking shell.
    try std.testing.expect(!r.command._detach);

    // Remove only the detach word; forward the remaining argv unchanged. The
    // e2e test verifies the child command line through `/proc/PID/cmdline`.
    const d = parse(&.{ "d", "start", "-d", "--sock", "/tmp/x.sock" });
    try std.testing.expect(d.command._detach);
    try std.testing.expectEqualStrings("/tmp/x.sock", d.command.sock.?);
    try std.testing.expect(parse(&.{ "d", "start", "--detach", "--cols", "100" }).command._detach);

    // A second `-d` is unknown rather than silently removed.
    const twice = parse(&.{ "d", "start", "-d", "-d" });
    try std.testing.expectEqualStrings("-d", twice.usage.unknown_arg);

    // Detach is positional and is not recognized after other start arguments.
    const late = parse(&.{ "d", "start", "--sock", "/tmp/x.sock", "-d" });
    try std.testing.expect(late == .usage);
    try std.testing.expectEqualStrings("-d", late.usage.unknown_arg);
    try std.testing.expectEqual(@as(u8, 2), usageCode(late.usage));

    // A token consumed as a value is never reinterpreted as a flag.
    const shell = parse(&.{ "d", "start", "-d", "--shell", "-d" });
    try std.testing.expect(shell.command._detach);
    try std.testing.expectEqualStrings("-d", shell.command.shell.?);
}

test "parseArgs: stop is a command and takes --sock" {
    const r = parse(&.{ "d", "stop" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .stop);
    try std.testing.expect(r.command.sock == null);

    const s = parse(&.{ "d", "stop", "--sock", "/tmp/x.sock" });
    try std.testing.expect(s.command._cmd == .stop);
    try std.testing.expectEqualStrings("/tmp/x.sock", s.command.sock.?);
}

test "parseArgs: stop, dump and stats take --quic as the other door, and never both doors" {
    // The path-less daemon's door (145807a2): the same `--quic HOST:PORT
    // [--key]` mux a spells, on the three verbs the daemon serves to a
    // QUIC client slot.
    const q = parse(&.{ "d", "stop", "--quic", "127.0.0.1:4433", "--key", "/k" });
    try std.testing.expect(q == .command);
    try std.testing.expect(q.command._cmd == .stop);
    try std.testing.expectEqualStrings("127.0.0.1:4433", q.command.quic.?);
    try std.testing.expectEqualStrings("/k", q.command.key.?);
    try std.testing.expect(parse(&.{ "d", "stats", "--quic", "box" }) == .command);
    try std.testing.expect(parse(&.{ "d", "dump", "--session", "a", "--quic", "box:1" }) == .command);

    // Two daemons named for one question is a refusal, not a preference.
    try std.testing.expect(parse(&.{ "d", "stop", "--sock", "/x", "--quic", "box" }).usage == .sock_with_quic);
    try std.testing.expect(parse(&.{ "d", "stats", "--quic", "box", "--sock", "/x" }).usage == .sock_with_quic);
    // `start` listens on both, so both stay legal there.
    try std.testing.expect(parse(&.{ "d", "start", "--sock", "/x", "--quic", "0.0.0.0:1" }) == .command);
    // And the key rule is unchanged: --key still wants --quic.
    try std.testing.expect(parse(&.{ "d", "stop", "--key", "/k" }).usage == .key_without_quic);

    // A verb with no QUIC door refuses the flag rather than dropping it:
    // `upgrade --quic HOST` used to upgrade the socket's daemon in silence.
    inline for (.{ "upgrade", "proxy", "endpoint" }) |verb| {
        const r = parse(&.{ "d", verb, "--quic", "box" });
        try std.testing.expect(r == .usage);
        try std.testing.expectEqualStrings("--quic", r.usage.unknown_arg);
    }
}

test "parseArgs: endpoint is a command and takes --sock" {
    const r = parse(&.{ "d", "endpoint" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .endpoint);
    try std.testing.expect(r.command.sock == null);

    const s = parse(&.{ "d", "endpoint", "--sock", "/tmp/x.sock" });
    try std.testing.expect(s.command._cmd == .endpoint);
    try std.testing.expectEqualStrings("/tmp/x.sock", s.command.sock.?);

    // Unknown flags fail, but shared value-taking flags still parse for this
    // command and are ignored. Only `keygen` rejects all trailing arguments.
    try std.testing.expect(parse(&.{ "d", "endpoint", "--quiet" }).usage == .unknown_arg);
    try std.testing.expect(parse(&.{ "d", "endpoint", "--cols", "100" }) == .command);
    const missing = parse(&.{ "d", "endpoint", "--sock" });
    try std.testing.expect(missing.usage == .missing_value);
    try std.testing.expectEqualStrings("--sock", missing.usage.missing_value);
}

test "parseArgs: a command-scoped option is rejected by every other command" {
    // `--start` lets a cold `mux HOST` start and query the daemon in one SSH
    // invocation.
    const on = parse(&.{ "d", "endpoint", "--start" });
    try std.testing.expect(on == .command);
    try std.testing.expect(on.command._cmd == .endpoint);
    try std.testing.expect(on.command.start);
    // Polling omits `--start`; inheriting true would start a daemon during every
    // read-only wall poll.
    try std.testing.expect(!parse(&.{ "d", "endpoint" }).command.start);

    // Check every command-table row so newly added commands cannot accidentally
    // inherit command-specific options. Rows that intentionally ignore all
    // arguments are excluded.
    const scoped = .{ .{ "--start", DaemonCommand.endpoint }, .{ "-d", DaemonCommand.start } };
    inline for (specs) |s| {
        if (s.flags == .ignored) continue;
        inline for (scoped) |w| {
            if (s.cmd != w[1]) {
                const r = parseArgs(&.{ "d", nameZ(s.name), w[0] });
                // expect() alone prints "expected true", which does not
                // say which verb let which word through.
                if (r != .usage) std.debug.print(
                    "`mux d {s} {s}` was accepted; {s} belongs to one verb\n",
                    .{ s.name, w[0], w[0] },
                );
                try std.testing.expect(r == .usage);
                try std.testing.expectEqual(@as(u8, 2), usageCode(r.usage));
            }
        }
    }
}

test "parseArgs: start --resume-fd N --check is the old daemon's dry run" {
    const r = parse(&.{ "d", "start", "--resume-fd", "7", "--check" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .start);
    try std.testing.expectEqual(@as(std.posix.fd_t, 7), r.command.resume_fd.?);
    try std.testing.expect(r.command.check);

    // The manifest descriptor must parse as an integer.
    try std.testing.expect(parse(&.{ "d", "start", "--resume-fd", "x" }).usage == .bad_value);

    const f = parse(&.{ "d", "start", "--resume-fd", "3", "--resume-fail-at", "session" });
    try std.testing.expectEqualStrings("session", f.command.resume_fail_at.?);

    // Ordinary startup must not enter resume or validation mode.
    const plain = parse(&.{ "d", "start" });
    try std.testing.expect(plain.command.resume_fd == null);
    try std.testing.expect(!plain.command.check);
}

test "failAtFrom: the flag beats the environment, and neither is no abort" {
    try std.testing.expectEqualStrings("session", failAtFrom("session", "daemon"));
    // E2e failure injection uses the environment because upgrade builds fixed
    // argv.
    try std.testing.expectEqualStrings("daemon", failAtFrom(null, "daemon"));
    try std.testing.expectEqualStrings("", failAtFrom(null, null));
}

test "rollbackKeepsEnv: the rollback does not inherit the abort that caused it" {
    // The rollback target must not inherit the failure injection that triggered
    // rollback.
    try std.testing.expect(!rollbackKeepsEnv("MUX_RESUME_FAIL_AT=session"));
    try std.testing.expect(rollbackKeepsEnv("MUX_SHELL_INTEGRATION=1"));
    // The name is a prefix of nothing else, but a variable that merely
    // starts with the same letters is not this one.
    try std.testing.expect(rollbackKeepsEnv("MUX_RESUME_FAIL_AT_NOT=1"));
}

test "parseArgs: upgrade is a command, and same-version is a flag it takes" {
    const r = parse(&.{ "d", "upgrade" });
    try std.testing.expect(r == .command);
    try std.testing.expect(r.command._cmd == .upgrade);
    // Strictly newer remains the default unless the exception is explicit.
    try std.testing.expect(!r.command.allow_same_version);

    const s = parse(&.{ "d", "upgrade", "--sock", "/tmp/x.sock", "--allow-same-version" });
    try std.testing.expect(s.command._cmd == .upgrade);
    try std.testing.expectEqualStrings("/tmp/x.sock", s.command.sock.?);
    try std.testing.expect(s.command.allow_same_version);
}

test "resumeRun: --check adopts nothing, so --resume-fail-at has nothing to abort" {
    const alloc = std.testing.allocator;

    const carrier = try server_os.anonFd("mux-resume-check-test");
    defer std.posix.close(carrier);

    var buf: std.ArrayList(u8) = .empty;
    defer buf.deinit(alloc);
    try upgrade.writeManifest(buf.writer(alloc), alloc, .{
        .writer_version = "0.0.1-99",
        // Use a non-executable rollback target so an incorrect rollback from
        // validation fails locally instead of replacing the test runner.
        .writer_path = "/nonexistent/mux",
        .sock_path = "/tmp/mux-resume-check-test.sock",
        .listener_fd = -1,
        .shellint_dir = null,
        .agent_dir = null,
        .shell = "/bin/sh",
        .shell_integration = false,
        .extra_env = &.{},
        .quic = .{},
        .counters = .{},
    }, &.{});
    var file = std.fs.File{ .handle = carrier };
    try file.writeAll(buf.items);

    const code = try resumeRun(alloc, .{
        ._cmd = .start,
        .check = true,
        .resume_fd = carrier,
        .resume_fail_at = "daemon",
    }, carrier);
    try std.testing.expectEqual(@as(u8, 0), code);
}

test "announceKeyFrom: MUX_KEY_FILE wins, and the default it skipped is not created" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();

    var ebuf: [280]u8 = undefined;
    var dbuf: [280]u8 = undefined;
    const env = try std.fmt.bufPrint(&ebuf, "{s}/env-key", .{tmp.path()});
    const dflt = try std.fmt.bufPrint(&dbuf, "{s}/cfg/mux/key", .{tmp.path()});
    try xdg.writeNewKey(env);

    const r = announceKeyFrom(env, dflt);
    try std.testing.expect(r == .key);
    // The loaded key must match the selected environment file exactly.
    var on_disk: [32]u8 = undefined;
    try std.testing.expectEqualSlices(u8, try std.fs.cwd().readFile(env, &on_disk), &r.key.bytes);

    // Selecting an environment key must not create an unused default credential
    // that a later daemon might select.
    try std.testing.expectError(error.FileNotFound, std.fs.cwd().access(dflt, .{}));
}

test "announceKeyFrom: the default is created when absent, and no path at all is no_path" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();

    var dbuf: [280]u8 = undefined;
    const dflt = try std.fmt.bufPrint(&dbuf, "{s}/cfg/mux/key", .{tmp.path()});

    // First use creates the default key automatically.
    const made = announceKeyFrom(null, dflt);
    try std.testing.expect(made == .key);
    const st = try std.fs.cwd().statFile(dflt);
    try std.testing.expectEqual(@as(u32, 0o600), @as(u32, @intCast(st.mode & 0o777)));

    // Subsequent calls load the same key instead of rotating it.
    const again = announceKeyFrom(null, dflt);
    try std.testing.expect(again == .key);
    try std.testing.expectEqualSlices(u8, &made.key.bytes, &again.key.bytes);

    try std.testing.expect(announceKeyFrom(null, null) == .no_path);
}

test "announceKeyFrom: a default that cannot be created reports the create, not the load" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();

    var robuf: [280]u8 = undefined;
    var dbuf: [280]u8 = undefined;
    const ro = try std.fmt.bufPrint(&robuf, "{s}/ro", .{tmp.path()});
    const dflt = try std.fmt.bufPrint(&dbuf, "{s}/mux/key", .{ro});
    try std.fs.cwd().makePath(ro);
    {
        var d = try std.fs.cwd().openDir(ro, .{ .iterate = true });
        defer d.close();
        try d.chmod(0o500);
    }
    // Left at 0500 for cleanup, deliberately: 0500 still grants read and
    // execute, so deleteTree can enter and list it, and removing the empty
    // directory itself needs write on the tmp ROOT, which is untouched.
    // Emptiness is not an assumption — it is the assertion below.

    // Swallowing the create error leaves the load to say `no such key file`,
    // which names the symptom and sends the reader looking for a file when the
    // story is a directory they cannot write.
    const r = announceKeyFrom(null, dflt);
    try std.testing.expect(r == .create_failed);
    try std.testing.expectEqual(error.AccessDenied, r.create_failed.err);
    try std.testing.expectEqualStrings(dflt, r.create_failed.path);
}

test "askEndpointPort: a socket nobody serves answers 0, quickly" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [280]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/absent.sock", .{tmp.path()});

    // Zero is the announce-none path, and reaching it FAST is the point: the
    // connect refusal is immediate, so the bounded wait is never entered. One
    // that polled to the deadline would answer the same 0 two seconds later.
    const t0 = std.time.milliTimestamp();
    try std.testing.expectEqual(@as(u16, 0), askEndpointPort(std.testing.allocator, sock));
    try std.testing.expect(std.time.milliTimestamp() - t0 < 500);
}

test "endpointCmd: polling an absent daemon does not start one" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [280]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/absent.sock", .{tmp.path()});
    // Capture output in a file so the test can verify that no announcement was
    // written without touching the test runner's stdout.
    var out_buf: [280]u8 = undefined;
    const out_path = try std.fmt.bufPrint(&out_buf, "{s}/announce", .{tmp.path()});
    const out = try std.fs.cwd().createFile(out_path, .{});
    defer out.close();

    try std.testing.expectEqual(
        @as(u8, 1),
        // False models background polling. The true startup path requires the
        // production XDG log path and is covered by e2e tests.
        try endpointCmd(std.testing.allocator, sock, out.handle, false),
    );
    // A read-only poll must not create a daemon or default session, especially
    // immediately after an explicit stop.
    try std.testing.expect(!sockpath.answers(sock));
    try std.testing.expectEqual(@as(u64, 0), (try out.stat()).size);
}

test "oneShotQuery: a socket nobody serves is exit 1" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [280]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/absent.sock", .{tmp.path()});

    // Unlike idempotent stop, dump and stats require a daemon response and
    // therefore return one for an absent socket.
    try std.testing.expectEqual(
        @as(u8, 1),
        try oneShotQuery(std.testing.allocator, .{ .sock = sock }, "dump", .debug_dump, "", .dump_reply),
    );
    try std.testing.expectEqual(
        @as(u8, 1),
        try oneShotQuery(std.testing.allocator, .{ .sock = sock }, "stats", .stats_req, "", .stats_reply),
    );
}

test "stopCmd: a socket path with nothing on it is exit 0, not a failure" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [280]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/absent.sock", .{tmp.path()});
    try std.testing.expectEqual(@as(u8, 0), try stopCmd(std.testing.allocator, .{ .sock = sock }));
}

test "peerPid: the kernel names the peer" {
    // Both socketpair endpoints belong to this process, so kernel credentials
    // must report the current pid.
    var sp: [2]i32 = undefined;
    try std.testing.expectEqual(@as(c_int, 0), std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM, 0, &sp));
    defer std.posix.close(sp[0]);
    defer std.posix.close(sp[1]);
    try std.testing.expectEqual(server_os.getpid(), peerPid(sp[0]).?);
}

test "waitPidGone: returns only once the OS has no such process" {
    // Use a grandchild because a direct child can remain as a zombie that signal
    // zero still finds. The shell exits after printing the sleeper pid, leaving
    // the reparented process to terminate independently.
    //
    // The sleeper's stdio goes to /dev/null so it does not hold the pipe this
    // test reads the pid through. Inheriting it made the read below block for
    // the sleeper's whole lifetime, so the pid was already dying by the time
    // the aliveness check ran and the wait proved nothing — on macOS the check
    // lost that race outright and the test failed at its first line. A second
    // of life, spent while `waitPidGone` polls, is what makes the wait mean
    // something.
    var child = std.process.Child.init(&.{ "sh", "-c", "sleep 1 >/dev/null 2>&1 </dev/null & echo $!" }, std.testing.allocator);
    child.stdout_behavior = .Pipe;
    try child.spawn();
    var buf: [32]u8 = undefined;
    const n = try child.stdout.?.readAll(&buf);
    _ = try child.wait();
    const pid = try std.fmt.parseInt(std.posix.pid_t, std.mem.trim(u8, buf[0..n], "\n "), 10);
    try std.posix.kill(pid, 0); // alive when we start, or the wait proves nothing
    try std.testing.expectEqual(@as(u8, 0), waitPidGone(std.testing.allocator, pid, "(test)", true));
    try std.testing.expectError(error.ProcessNotFound, std.posix.kill(pid, 0));
}

test "shellIntegrationEnabled: an unset environment means off" {
    // Default off because the zsh integration changes `~/.zshenv` handling and
    // the Bash integration replaces the DEBUG trap.
    try std.testing.expect(!shellIntegrationEnabled(null));
}

test "shellIntegrationEnabled: `1` and nothing else turns it on" {
    try std.testing.expect(shellIntegrationEnabled("1"));
    // Every other value remains off, including stale `=0` configurations from
    // the previous opt-out behavior.
    try std.testing.expect(!shellIntegrationEnabled("0"));
    try std.testing.expect(!shellIntegrationEnabled(""));
    try std.testing.expect(!shellIntegrationEnabled("true"));
    try std.testing.expect(!shellIntegrationEnabled("yes"));
}

fn silentProgress() StartProgress {
    // StartProgress that writes to /dev/null keeps test output clean while the
    // pinned-output case below captures a pipe instead.
    const f = std.fs.cwd().openFile("/dev/null", .{ .mode = .write_only }) catch unreachable;
    return .{ .fd = f.handle, .prefix = "test", .tty = false };
}

test "start -d: an answering socket is already_running, nothing spawned" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [128]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/live.sock", .{tmp.path()});

    const addr = try std.net.Address.initUnix(sock);
    var server = try addr.listen(.{});
    defer server.deinit();

    // Capture progress and verify that the already-running path is silent.
    const pipe = try std.posix.pipe();
    defer std.posix.close(pipe[0]);
    const progress: StartProgress = .{ .fd = pipe[1], .prefix = "test", .tty = false };

    const r = try forkDaemon(
        std.testing.allocator,
        "/definitely/not/consulted",
        &.{},
        sock,
        progress,
        200,
        null,
    );
    try std.testing.expectEqual(StartOutcome.already_running, r);

    std.posix.close(pipe[1]);
    var out: [64]u8 = undefined;
    try std.testing.expectEqual(@as(usize, 0), try std.posix.read(pipe[0], &out));
}

test "start -d: a socket this process may not reach is not `already running`" {
    // chmod does not bite root; as root the connect succeeds and the
    // premise of the test is gone.
    if (std.posix.geteuid() == 0) return error.SkipZigTest;

    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [128]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/mode0.sock", .{tmp.path()});

    const addr = try std.net.Address.initUnix(sock);
    var server = try addr.listen(.{});
    defer server.deinit();
    try std.posix.fchmodat(std.posix.AT.FDCWD, sock, 0, 0);

    // Reaching `SpawnFailed` proves the inaccessible socket was not mistaken for
    // an already-running daemon.
    try std.testing.expectError(error.SpawnFailed, forkDaemon(
        std.testing.allocator,
        "/no/such/mux",
        &.{},
        sock,
        silentProgress(),
        200,
        null,
    ));
}

test "start -d: a missing binary is SpawnFailed before any fork" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var buf: [128]u8 = undefined;
    const sock = try std.fmt.bufPrint(&buf, "{s}/none.sock", .{tmp.path()});
    try std.testing.expectError(error.SpawnFailed, forkDaemon(
        std.testing.allocator,
        "/no/such/mux",
        &.{},
        sock,
        silentProgress(),
        200,
        null,
    ));
}

test "start -d: a child that dies young is reported, not panicked on" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var pbuf: [128]u8 = undefined;
    var sbuf: [128]u8 = undefined;
    var lbuf: [128]u8 = undefined;
    const stub = try std.fmt.bufPrint(&pbuf, "{s}/dies.sh", .{tmp.path()});
    const sock = try std.fmt.bufPrint(&sbuf, "{s}/dead.sock", .{tmp.path()});
    const log = try std.fmt.bufPrint(&lbuf, "{s}/logs/muxd.log", .{tmp.path()});

    // Model a child that exits before binding. The poll loop must reap it once
    // and continue to the deadline without a second waitpid call.
    try tmp.dir.writeFile(.{ .sub_path = "dies.sh", .data = "#!/bin/sh\nexit 3\n" });
    const f = try tmp.dir.openFile("dies.sh", .{});
    try f.chmod(0o755);
    f.close();

    try std.testing.expectError(error.NeverAnswered, forkDaemon(
        std.testing.allocator,
        stub,
        &.{},
        sock,
        silentProgress(),
        300,
        log,
    ));
    // The log must remain available for the startup-failure diagnostic.
    const log_st = try std.fs.cwd().statFile(log);
    try std.testing.expectEqual(@as(u32, 0o600), @as(u32, @intCast(log_st.mode & 0o777)));
}

test "start -d: a binary that never binds is NeverAnswered, pid left alive" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var pbuf: [128]u8 = undefined;
    var sbuf: [128]u8 = undefined;
    var lbuf: [128]u8 = undefined;
    const stub = try std.fmt.bufPrint(&pbuf, "{s}/stub.sh", .{tmp.path()});
    const sock = try std.fmt.bufPrint(&sbuf, "{s}/never.sock", .{tmp.path()});
    const log = try std.fmt.bufPrint(&lbuf, "{s}/logs/muxd.log", .{tmp.path()});

    // Exec the sleeper so the tracked pid belongs to the persistent process,
    // not an intermediate shell.
    try tmp.dir.writeFile(.{ .sub_path = "stub.sh", .data = "#!/bin/sh\nexec sleep 30\n" });
    const f = try tmp.dir.openFile("stub.sh", .{});
    try f.chmod(0o755);
    f.close();

    // Override the log path because tests cannot change XDG_STATE_HOME. The
    // nested path also verifies parent-directory creation.
    const t0 = std.time.milliTimestamp();
    try std.testing.expectError(error.NeverAnswered, forkDaemon(
        std.testing.allocator,
        stub,
        &.{},
        sock,
        silentProgress(),
        300,
        log,
    ));
    // Startup creates the log before fork, so it exists even when the child
    // writes nothing.
    const log_st = try std.fs.cwd().statFile(log);
    try std.testing.expectEqual(@as(u32, 0o600), @as(u32, @intCast(log_st.mode & 0o777)));
    // It waited the deadline out rather than bailing early...
    try std.testing.expect(std.time.milliTimestamp() - t0 >= 300);

    // Deadline expiry must not kill the spawned process. Use the exact tracked
    // pid for cleanup to avoid affecting unrelated processes.
    try std.testing.expect(last_spawned_pid != 0);
    // waitpid with NOHANG returning pid 0 means "child exists, still
    // running", which is the assertion; a reaped or dead child returns its
    // own pid instead.
    try std.testing.expectEqual(
        @as(std.posix.pid_t, 0),
        std.posix.waitpid(last_spawned_pid, std.posix.W.NOHANG).pid,
    );
    std.posix.kill(last_spawned_pid, std.posix.SIG.KILL) catch {};
    _ = std.posix.waitpid(last_spawned_pid, 0);
}

test "start -d: the child executes the supplied path without searching PATH" {
    const testtmp = @import("testtmp");
    var tmp = try testtmp.TmpDir.make();
    defer tmp.cleanup();
    var pbuf: [160]u8 = undefined;
    var sbuf: [160]u8 = undefined;
    var lbuf: [160]u8 = undefined;
    var rbuf: [160]u8 = undefined;
    const stub = try std.fmt.bufPrint(&pbuf, "{s}/stub.sh", .{tmp.path()});
    const sock = try std.fmt.bufPrint(&sbuf, "{s}/self.sock", .{tmp.path()});
    const log = try std.fmt.bufPrint(&lbuf, "{s}/logs/mux.log", .{tmp.path()});
    const seen = try std.fmt.bufPrint(&rbuf, "{s}/child.exe", .{tmp.path()});

    // Record `$0` and `$*` to verify both the exact executable path and the
    // explicit `d start` mode words used for process listings.
    var script: [512]u8 = undefined;
    try tmp.dir.writeFile(.{
        .sub_path = "stub.sh",
        .data = try std.fmt.bufPrint(&script,
            \\#!/bin/sh
            \\printf '%s|%s' "$0" "$*" > "{s}"
            \\exec sleep 30
            \\
        , .{seen}),
    });
    const f = try tmp.dir.openFile("stub.sh", .{});
    try f.chmod(0o755);
    f.close();

    try std.testing.expectError(error.NeverAnswered, forkDaemon(
        std.testing.allocator,
        stub,
        &.{},
        sock,
        silentProgress(),
        400,
        log,
    ));
    defer {
        std.posix.kill(last_spawned_pid, std.posix.SIG.KILL) catch {};
        _ = std.posix.waitpid(last_spawned_pid, 0);
    }

    var got_buf: [std.fs.max_path_bytes]u8 = undefined;
    // Convert a missing marker into a readable mismatch so the assertion still
    // identifies an unexpected executable.
    const got = std.fs.cwd().readFile(seen, &got_buf) catch "<the child ran something else>";
    var want_buf: [200]u8 = undefined;
    const want = try std.fmt.bufPrint(&want_buf, "{s}|d start", .{stub});
    try std.testing.expectEqualStrings(want, std.mem.trimRight(u8, got, "\n"));
}

// Ensure every public declaration is semantically analyzed during tests;
// `std.meta.declarations` does not include private declarations.
test {
    std.testing.refAllDeclsRecursive(@This());
}