Skip to content
zit Artifact [8456e03137]
Login

Artifact [8456e03137]

Artifact 8456e031372f1affcafc2f4c40655a3f97f3bb8266d8bb6371859602fbf918d2:


//! RED coverage for Task 10's transactional `zit import-git` command.
//!
//! This file deliberately names the smallest testable production seam that does
//! not execute a shell command or contact a remote:
//!
//!   `git_import_cli.run(gpa, io, request, streams, dependencies) -> cli.Exit`
//!
//! `dependencies` supplies a `ChildFactory`/`ChildSession` and filesystem hooks.
//! A `ChildSession` must expose its stdout reader plus `terminate`, `drainStderr`,
//! and `reap`; each lifecycle operation is observable in the supplied fake. The
//! real CLI dispatch will construct OS-backed dependencies. The filesystem hook
//! is only needed to deterministically fail publication flush/close/sync/rename
//! boundaries; ordinary scenarios exercise a real `std.testing.tmpDir`.
//!
//! The opt-in suite intentionally remains RED until Task 10 provides command
//! preflight, real-pipe lifecycle, and publication-boundary fault seams. It is
//! not part of `zig build test`.

const std = @import("std");
const builtin = @import("builtin");
const cli = @import("cli.zig");
const fast_export = @import("zit").git_fast_export;
const import_cli = @import("git_import_cli.zig");
const checkout = @import("zit").checkout;
const artifact = @import("zit").artifact;
const git_import = @import("zit").git_import;
const logstore = @import("zit").logstore;
const storage = @import("zit").storage;
const helper_options = @import("git_import_cli_options");

const valid_export =
    "feature done\n" ++
    "blob\n" ++
    "mark :1\n" ++
    "data 5\n" ++
    "hello\n" ++
    "commit refs/heads/main\n" ++
    "mark :2\n" ++
    "author A <[email protected]> 0 +0000\n" ++
    "committer A <[email protected]> 0 +0000\n" ++
    "data 4\n" ++
    "init\n" ++
    "M 100644 :1 README\n" ++
    "\n" ++
    "done\n";

const valid_export_with_remote_ref =
    "feature done\n" ++
    "blob\n" ++
    "mark :1\n" ++
    "data 5\n" ++
    "hello\n" ++
    "commit refs/heads/main\n" ++
    "mark :2\n" ++
    "author A <[email protected]> 0 +0000\n" ++
    "committer A <[email protected]> 0 +0000\n" ++
    "data 4\n" ++
    "init\n" ++
    "M 100644 :1 README\n" ++
    "\n" ++
    "reset refs/remotes/origin/main\n" ++
    "from :2\n" ++
    "\n" ++
    "done\n";

const valid_export_with_oids =
    "feature done\n" ++
    "blob\n" ++
    "mark :1\n" ++
    "original-oid 100b93820ade4c16225673b4ca62bb3ade63c313\n" ++
    "data 5\n" ++
    "hello\n" ++
    "commit refs/heads/main\n" ++
    "mark :2\n" ++
    "original-oid ed9b38a10070b504cecfacd892507af78fcb24a4\n" ++
    "author A <[email protected]> 0 +0000\n" ++
    "committer A <[email protected]> 0 +0000\n" ++
    "data 4\n" ++
    "init\n" ++
    "M 100644 :1 README\n" ++
    "\n" ++
    "done\n";

const malformed_export =
    "feature done\n" ++
    "commit refs/heads/main\n" ++
    "mark :not-a-number\n";

const marked_tag_export =
    "feature done\n" ++
    "blob\n" ++
    "mark :1\n" ++
    "data 5\n" ++
    "hello\n" ++
    "commit refs/heads/main\n" ++
    "mark :2\n" ++
    "author A <[email protected]> 0 +0000\n" ++
    "committer A <[email protected]> 0 +0000\n" ++
    "data 4\n" ++
    "init\n" ++
    "M 100644 :1 README.md\n" ++
    "\n" ++
    "tag v1\n" ++
    "mark :3\n" ++
    "from :2\n" ++
    "tagger A <[email protected]> 0 +0000\n" ++
    "data 0\n" ++
    "\n" ++
    "done\n";

const Result = struct {
    code: cli.Exit,
    out: []const u8,
    err: []const u8,
    lifecycle: import_cli.ChildLifecycle,
};

/// The wished test adapter. Task 10's production implementation owns no fake
/// process: it consumes this narrow interface, while the CLI passes the
/// OS-backed implementation and this file passes a scripted one.
const LifecycleFault = enum { terminate, drain_stderr, reap };

const ScriptedGit = struct {
    fast_export: []const u8,
    export_exit: u8 = 0,
    stderr: []const u8 = "",
    launched: usize = 0,
    terminated: usize = 0,
    stderr_drained: usize = 0,
    reaped: usize = 0,
    lifecycle_fault: ?LifecycleFault = null,
    limits: import_cli.ImportLimits = .{},
    /// What preflight's §6.3 analysis would have returned. Null is "the
    /// analysis produced nothing", which is what every test that does not care
    /// about the analyzed path wants.
    analyzed_submanifest_k: ?u4 = null,

    fn dependencies(self: *ScriptedGit, publication: import_cli.PublicationHooks) import_cli.Dependencies {
        return .{
            .git = .{
                .context = self,
                .vtable = &.{
                    .start_export = startExport,
                    .terminate = terminate,
                    .drain_stderr = drainStderr,
                    .reap = reap,
                },
            },
            .publication = publication,
            .limits = self.limits,
        };
    }

    fn startExport(context: *anyopaque, _: []const u8) !import_cli.ExportPipes {
        const self: *ScriptedGit = @ptrCast(@alignCast(context));
        self.launched += 1;
        return .{
            .stdout = .fixed(self.fast_export),
            .stderr = .fixed(self.stderr),
            .exit_code = self.export_exit,
            .analyzed_submanifest_k = self.analyzed_submanifest_k,
        };
    }

    fn terminate(context: *anyopaque) !void {
        const self: *ScriptedGit = @ptrCast(@alignCast(context));
        self.terminated += 1;
        if (self.lifecycle_fault == .terminate) return error.InjectedLifecycleFailure;
    }

    fn drainStderr(context: *anyopaque) !void {
        const self: *ScriptedGit = @ptrCast(@alignCast(context));
        self.stderr_drained += 1;
        if (self.lifecycle_fault == .drain_stderr) return error.InjectedLifecycleFailure;
    }

    fn reap(context: *anyopaque) !void {
        const self: *ScriptedGit = @ptrCast(@alignCast(context));
        self.reaped += 1;
        if (self.lifecycle_fault == .reap) return error.InjectedLifecycleFailure;
    }
};

fn runFixture(
    tmp: std.testing.TmpDir,
    source: []const u8,
    destination: []const u8,
    quiet: bool,
    git: *ScriptedGit,
    publication: import_cli.PublicationHooks,
) Result {
    return runFixtureShaped(tmp, source, destination, quiet, git, publication, .{ .tree = 5 });
}

/// `runFixture` with the one request field these tests vary. Everything else --
/// source, dependencies, streams, attribution, default branch -- is held
/// identical on purpose: the whole point of the flag is that flat and tree
/// imports differ in tree emission and in nothing else.
fn runFixtureShaped(
    tmp: std.testing.TmpDir,
    source: []const u8,
    destination: []const u8,
    quiet: bool,
    git: *ScriptedGit,
    publication: import_cli.PublicationHooks,
    submanifest: git_import.SubmanifestChoice,
) Result {
    const Capture = struct {
        var out_storage: [4096]u8 = undefined;
        var err_storage: [4096]u8 = undefined;
    };
    var out = std.Io.Writer.fixed(&Capture.out_storage);
    var err = std.Io.Writer.fixed(&Capture.err_storage);
    const streams: cli.Streams = .{ .out = &out, .err = &err };

    const code = import_cli.run(
        std.testing.allocator,
        std.testing.io,
        .{
            .source = source,
            .destination_parent = tmp.dir,
            .destination_name = destination,
            .submanifest = submanifest,
            .attribution = .committer,
            .default_branch = null,
            .quiet = quiet,
        },
        streams,
        git.dependencies(publication),
    );

    return .{
        .code = code,
        .out = out.buffered(),
        .err = err.buffered(),
        .lifecycle = .{
            .launched = git.launched,
            .terminated = git.terminated,
            .stderr_drained = git.stderr_drained,
            .reaped = git.reaped,
        },
    };
}
fn retainedBytesForExport(bytes: []const u8) !u64 {
    var reader = fast_export.Reader.init(std.testing.allocator, .fixed(bytes), .{
        .max_line = 1024 * 1024,
        .max_data = 1024 * 1024 * 1024,
        .max_path = 4096,
    });
    defer reader.deinit();

    var retained_bytes: u64 = 0;
    while (try reader.next()) |lease| {
        const charge = blk: {
            var event = lease;
            defer event.deinit();
            break :blk try import_cli.retainedByteCharge(event.value);
        };
        retained_bytes = try std.math.add(u64, retained_bytes, charge);
    }
    return retained_bytes;
}

fn exists(io: std.Io, dir: std.Io.Dir, path: []const u8) bool {
    var file = dir.openFile(io, path, .{}) catch return false;
    file.close(io);
    return true;
}
fn directoryExists(io: std.Io, dir: std.Io.Dir, path: []const u8) bool {
    var child = dir.openDir(io, path, .{}) catch return false;
    child.close(io);
    return true;
}

fn expectNoPublication(io: std.Io, tmp: std.testing.TmpDir, destination: []const u8) !void {
    try std.testing.expect(!directoryExists(io, tmp.dir, destination));
    try std.testing.expect(!exists(io, tmp.dir, ".zit-import-map.jsonl"));
    var entries = tmp.dir.iterate();
    while (try entries.next(io)) |entry| {
        try std.testing.expect(!std.mem.startsWith(u8, entry.name, ".zit-import-tmp-"));
    }
}

test "import-git publishes .zit refs and map together only after a complete export" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var git: ScriptedGit = .{ .fast_export = valid_export, .stderr = "Enumerating objects\n" };
    const result = runFixture(
        tmp,
        "fixture.git",
        "imported",
        false,
        &git,
        .real_filesystem,
    );

    try std.testing.expectEqual(cli.Exit.ok, result.code);
    try std.testing.expectEqualStrings("imported\n", result.out);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "import-git:") != null);
    var destination = try tmp.dir.openDir(std.testing.io, "imported", .{});
    defer destination.close(std.testing.io);
    try std.testing.expect(exists(std.testing.io, destination, ".zit"));
    try std.testing.expect(exists(std.testing.io, destination, "refs"));
    try std.testing.expect(exists(std.testing.io, destination, ".zit-import-map.jsonl"));
    var checkout_buf: [checkout.max_ck_payload + 1]u8 = undefined;
    const imported_checkout = (try checkout.read(std.testing.io, destination, &checkout_buf)).?;
    try std.testing.expectEqualStrings("", imported_checkout.version);
    try std.testing.expectEqualStrings("main", imported_checkout.branch);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.launched);
    try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
}

test "import-git finalizes remote refs emitted with all refs" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var git: ScriptedGit = .{ .fast_export = valid_export_with_remote_ref };
    const result = runFixture(tmp, "fixture.git", "with-remote", false, &git, .real_filesystem);

    try std.testing.expectEqual(cli.Exit.ok, result.code);
    var destination = try tmp.dir.openDir(std.testing.io, "with-remote", .{});
    defer destination.close(std.testing.io);
    // One sorted packed file, not a directory tree. Reading the bytes rather
    // than probing for paths is the point: the file's whole job is to be
    // legible, and its ordering is what makes two imports comparable.
    const packed_refs = try destination.readFileAlloc(std.testing.io, "refs", std.testing.allocator, .limited(4096));
    defer std.testing.allocator.free(packed_refs);
    try std.testing.expectEqualStrings(
        import_cli.packed_refs_header ++
            "sym-heads/main 809fa79c883c54adff3a115e235f83f60327e4d2\n" ++
            "sym-refs/remotes/origin/main 809fa79c883c54adff3a115e235f83f60327e4d2\n",
        packed_refs,
    );
}

test "import-git refuses an existing destination before Git starts" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    try tmp.dir.createDir(std.testing.io, "already-there", .default_dir);

    var git: ScriptedGit = .{ .fast_export = valid_export };
    const result = runFixture(tmp, "fixture.git", "already-there", false, &git, .real_filesystem);

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expectEqualStrings("", result.out);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "already exists") != null);
    try std.testing.expectEqual(@as(usize, 0), result.lifecycle.launched);
    try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated);
    try std.testing.expectEqual(@as(usize, 0), result.lifecycle.stderr_drained);
    try std.testing.expectEqual(@as(usize, 0), result.lifecycle.reaped);
}

test "import-git aborts malformed export by terminating draining and reaping once" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var git: ScriptedGit = .{ .fast_export = malformed_export, .stderr = "fatal fixture diagnostic\n" };
    const result = runFixture(tmp, "fixture.git", "broken", false, &git, .real_filesystem);

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expectEqualStrings("", result.out);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "fatal fixture diagnostic") != null);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.launched);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
    try expectNoPublication(std.testing.io, tmp, "broken");
}

test "import-git removes all temporary output when storage creation fails" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var git: ScriptedGit = .{ .fast_export = valid_export };
    const result = runFixture(
        tmp,
        "fixture.git",
        "storage-failed",
        false,
        &git,
        .{ .fail_at = .create_store },
    );

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expectEqualStrings("", result.out);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.launched);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
    try expectNoPublication(std.testing.io, tmp, "storage-failed");
}

test "import-git removes all temporary output when publication flush or rename fails" {
    const failures = [_]import_cli.PublicationHooks.FailurePoint{ .flush_store, .sync_map, .rename_no_replace };
    for (failures) |failure| {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var git: ScriptedGit = .{ .fast_export = valid_export };
        const result = runFixture(
            tmp,
            "fixture.git",
            "publication-failed",
            false,
            &git,
            .{ .fail_at = failure },
        );

        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expectEqualStrings("", result.out);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
        try expectNoPublication(std.testing.io, tmp, "publication-failed");
    }
}

test "import-git writes only a machine-readable destination to stdout and progress to stderr" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var git: ScriptedGit = .{ .fast_export = valid_export, .stderr = "git progress\n" };
    const result = runFixture(tmp, "fixture.git", "machine", false, &git, .real_filesystem);

    try std.testing.expectEqual(cli.Exit.ok, result.code);
    try std.testing.expectEqualStrings("machine\n", result.out);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "git progress") != null);
    try std.testing.expect(std.mem.indexOf(u8, result.out, "progress") == null);
}

test "import-git --quiet suppresses progress but not diagnostics" {
    var quiet_tmp = std.testing.tmpDir(.{});
    defer quiet_tmp.cleanup();
    var quiet_git: ScriptedGit = .{ .fast_export = valid_export, .stderr = "git progress\n" };
    const quiet = runFixture(quiet_tmp, "fixture.git", "quiet", true, &quiet_git, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, quiet.code);
    try std.testing.expectEqualStrings("quiet\n", quiet.out);
    try std.testing.expectEqualStrings("", quiet.err);

    var error_tmp = std.testing.tmpDir(.{});
    defer error_tmp.cleanup();
    var error_git: ScriptedGit = .{ .fast_export = malformed_export, .stderr = "fatal fixture diagnostic\n" };
    const failed = runFixture(error_tmp, "fixture.git", "quiet-error", true, &error_git, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.failure, failed.code);
    try std.testing.expectEqualStrings("", failed.out);
    try std.testing.expect(std.mem.indexOf(u8, failed.err, "fatal fixture diagnostic") != null);
}

// The remaining tests specify the Task 10 seam not yet present in
// `git_import_cli`: CommandScript records every preflight/export invocation,
// supplies bounded stdout/stderr streams, and exposes deterministic
// cancellation and publication-boundary faults. Production dispatch constructs
// the OS adapter; tests use no shell or network.
fn scripted(
    tmp: std.testing.TmpDir,
    script: *import_cli.CommandScript,
    destination: []const u8,
    quiet: bool,
    fault: import_cli.PublicationHooks,
) import_cli.ScriptResult {
    return import_cli.runScripted(
        std.testing.allocator,
        std.testing.io,
        .{
            .source = "fixture.git",
            .destination_parent = tmp.dir,
            .destination_name = destination,
            .submanifest = .{ .tree = 5 },
            .attribution = .committer,
            .default_branch = null,
            .quiet = quiet,
        },
        script,
        fault,
    );
}

const ProcessResult = struct {
    code: cli.Exit,
    out: []const u8,
    err: []const u8,
    exit_code: u8,
    reaped: bool,
    reap_calls: usize,
    child_gone: bool,
    stderr_bytes_drained: usize,
    retained_diagnostic_len: usize,
};

/// Drives the real ProductionGit session against the build-wired helper. The
/// only substitution is argv; spawning, pipe adapters, concurrent diagnostic
/// drain, terminate, join, and wait are the production implementation.
fn runProductionPipe(
    tmp: std.testing.TmpDir,
    destination: []const u8,
    mode: []const u8,
) !ProcessResult {
    var threaded = std.Io.Threaded.init(std.testing.allocator, .{});
    defer threaded.deinit();
    const io = threaded.io();
    var session = import_cli.ProductionGit.initForTest(
        std.testing.allocator,
        io,
        &.{ helper_options.git_import_pipe_helper, mode },
    );
    const Capture = struct {
        var out_storage: [4096]u8 = undefined;
        var err_storage: [4096]u8 = undefined;
    };
    var out = std.Io.Writer.fixed(&Capture.out_storage);
    var err = std.Io.Writer.fixed(&Capture.err_storage);
    const code = import_cli.run(
        std.testing.allocator,
        io,
        .{
            .source = "fixture.git",
            .destination_parent = tmp.dir,
            .destination_name = destination,
        },
        .{ .out = &out, .err = &err },
        session.dependencies(.real_filesystem),
    );
    return .{
        .code = code,
        .out = out.buffered(),
        .err = err.buffered(),
        .exit_code = session.exit_code,
        .reaped = session.reaped,
        .child_gone = session.child == null,
        .stderr_bytes_drained = session.stderr_bytes_drained,
        .reap_calls = session.reap_calls,
        .retained_diagnostic_len = session.stderr_ring_len,
    };
}
fn runGit(io: std.Io, argv: []const []const u8) !void {
    const result = try std.process.run(std.testing.allocator, io, .{ .argv = argv });
    defer std.testing.allocator.free(result.stdout);
    defer std.testing.allocator.free(result.stderr);
    switch (result.term) {
        .exited => |code| if (code == 0) return,
        else => {},
    }
    return error.GitSetupFailed;
}

fn writeFixtureFile(io: std.Io, dir: std.Io.Dir, path: []const u8, bytes: []const u8) !void {
    var file = try dir.createFile(io, path, .{ .read = true, .truncate = true });
    defer file.close(io);
    try file.writeStreamingAll(io, bytes);
}

fn runProductionPreflight(
    tmp: std.testing.TmpDir,
    source: []const u8,
    destination: []const u8,
) cli.Exit {
    const Capture = struct {
        var out_storage: [1024]u8 = undefined;
        var err_storage: [1024]u8 = undefined;
    };
    var out = std.Io.Writer.fixed(&Capture.out_storage);
    var err = std.Io.Writer.fixed(&Capture.err_storage);
    var session = import_cli.ProductionGit.init(std.testing.allocator, std.testing.io);
    defer session.deinit();
    return import_cli.run(
        std.testing.allocator,
        std.testing.io,
        .{
            .source = source,
            .destination_parent = tmp.dir,
            .destination_name = destination,
        },
        .{ .out = &out, .err = &err },
        session.dependencies(.real_filesystem),
    );
}

test "import-git parser leaves k unset for analysis and is a real dispatched verb" {
    // Absent means "analyze", not "5": the parser must not manufacture a pin
    // the runner would then be unable to distinguish from an operator's.
    const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" });
    try std.testing.expectEqual(@as(?u4, null), defaulted.submanifest_k);
    try std.testing.expectEqual(cli.Verb.import_git, cli.verbOf("import-git"));
    try std.testing.expect(!cli.isPlanned("import-git"));

    const parsed = try cli.parseImportGitArgs(&.{
        "--submanifest-k",  "7",
        "--attribution",    "author",
        "--default-branch", "main",
        "--quiet",          "fixture.git",
        "dest",
    });
    try std.testing.expectEqual(@as(?u4, 7), parsed.submanifest_k);
    try std.testing.expectEqual(.author, parsed.attribution);
    try std.testing.expectEqualStrings("main", parsed.default_branch.?);
    try std.testing.expect(parsed.quiet);
    const min_k = try cli.parseImportGitArgs(&.{ "--submanifest-k", "1", "fixture.git", "dest" });
    try std.testing.expectEqual(@as(?u4, 1), min_k.submanifest_k);
    const max_k = try cli.parseImportGitArgs(&.{ "--submanifest-k", "12", "fixture.git", "dest" });
    try std.testing.expectEqual(@as(?u4, 12), max_k.submanifest_k);
    for ([_][]const u8{ "0", "13", "14", "15" }) |value| {
        try std.testing.expectError(
            cli.ImportGitParseError.InvalidSubmanifestK,
            cli.parseImportGitArgs(&.{ "--submanifest-k", value, "fixture.git", "dest" }),
        );
    }
}

test "import-git parser accepts --no-submanifests and refuses it paired with a pinned k" {
    const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" });
    try std.testing.expect(!defaulted.no_submanifests);

    // The flag declines the mechanism; it does not manufacture a k, and it must
    // not be confusable with one -- `submanifest_k` stays absent.
    const flat = try cli.parseImportGitArgs(&.{ "--no-submanifests", "fixture.git", "dest" });
    try std.testing.expect(flat.no_submanifests);
    try std.testing.expectEqual(@as(?u4, null), flat.submanifest_k);

    // BOTH orders. An in-arm check would only fire for whichever token happened
    // to be seen second, which is exactly the bug this pairing invites.
    try std.testing.expectError(
        cli.ImportGitParseError.ConflictingSubmanifestOptions,
        cli.parseImportGitArgs(&.{ "--no-submanifests", "--submanifest-k", "5", "fixture.git", "dest" }),
    );
    try std.testing.expectError(
        cli.ImportGitParseError.ConflictingSubmanifestOptions,
        cli.parseImportGitArgs(&.{ "--submanifest-k", "5", "--no-submanifests", "fixture.git", "dest" }),
    );

    // A malformed token still reports its own cause: the conflict is ranked
    // below `first_err`, so an invalid k is not masked by the pairing.
    try std.testing.expectError(
        cli.ImportGitParseError.InvalidSubmanifestK,
        cli.parseImportGitArgs(&.{ "--submanifest-k", "0", "--no-submanifests", "fixture.git", "dest" }),
    );

    // Same shape as every other boolean flag on this verb.
    try std.testing.expectError(
        cli.ImportGitParseError.RepeatedOption,
        cli.parseImportGitArgs(&.{ "--no-submanifests", "--no-submanifests", "fixture.git", "dest" }),
    );

    // Help wins over the conflict, so `--help` stays informative no matter what
    // else is on the line.
    const helped = try cli.parseImportGitArgs(&.{ "--no-submanifests", "--submanifest-k", "5", "--help" });
    try std.testing.expect(helped.help);

    // It composes with every other behavioral flag; none of them interact.
    const composed = try cli.parseImportGitArgs(&.{
        "--no-submanifests", "--refuse-signed",
        "--attribution",     "author",
        "--default-branch",  "trunk",
        "--quiet",           "fixture.git",
        "dest",
    });
    try std.testing.expect(composed.no_submanifests);
    try std.testing.expect(composed.refuse_signed);
    try std.testing.expectEqual(.author, composed.attribution);
    try std.testing.expectEqualStrings("trunk", composed.default_branch.?);
    try std.testing.expect(composed.quiet);
}

test "import-git parser exposes the export payload budget as an operator flag" {
    // Every ceiling an honest source can reach must be raisable without a
    // recompile. This one budget carries both the cumulative payload limit and
    // the derived per-commit file-command ceiling, so it is the only knob the
    // verb needs.
    const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" });
    try std.testing.expectEqual(@as(?u64, null), defaulted.max_export_payload_bytes);

    const raised = try cli.parseImportGitArgs(&.{
        "--max-export-payload-bytes", "68719476736",
        "fixture.git",                "dest",
    });
    try std.testing.expectEqual(@as(?u64, 68719476736), raised.max_export_payload_bytes);

    // Zero derives a per-commit ceiling of zero: a budget no import can ever
    // satisfy is a typo, not a policy.
    try std.testing.expectError(
        cli.ImportGitParseError.InvalidExportPayloadBytes,
        cli.parseImportGitArgs(&.{ "--max-export-payload-bytes", "0", "fixture.git", "dest" }),
    );
    try std.testing.expectError(
        cli.ImportGitParseError.InvalidExportPayloadBytes,
        cli.parseImportGitArgs(&.{ "--max-export-payload-bytes", "many", "fixture.git", "dest" }),
    );
    try std.testing.expectError(
        cli.ImportGitParseError.MissingOptionValue,
        cli.parseImportGitArgs(&.{ "fixture.git", "dest", "--max-export-payload-bytes" }),
    );
    try std.testing.expectError(
        cli.ImportGitParseError.RepeatedOption,
        cli.parseImportGitArgs(&.{
            "--max-export-payload-bytes", "1024",
            "--max-export-payload-bytes", "2048",
            "fixture.git",                "dest",
        }),
    );
}

/// Whether `token` appears anywhere in `argv`. Positional indices would encode
/// the argv's shape, which is not what this asserts.
fn argvHas(argv: []const []const u8, token: []const u8) bool {
    for (argv) |candidate| {
        if (std.mem.eql(u8, candidate, token)) return true;
    }
    return false;
}

test "import-git strips signatures by default and aborts only under --refuse-signed" {
    // Fossil has no signature card, so refusing by default withheld the import
    // without saving anything. The default must therefore strip.
    const defaulted = try cli.parseImportGitArgs(&.{ "fixture.git", "dest" });
    try std.testing.expect(!defaulted.refuse_signed);
    const stripping = import_cli.exportArgv("fixture.git", defaulted.refuse_signed);
    try std.testing.expect(argvHas(&stripping, "--signed-commits=strip"));
    try std.testing.expect(argvHas(&stripping, "--signed-tags=strip"));
    try std.testing.expect(!argvHas(&stripping, "--signed-commits=abort"));
    try std.testing.expect(!argvHas(&stripping, "--signed-tags=abort"));

    // The opt-out restores the refusal, and nothing else about the argv moves.
    const opted_out = try cli.parseImportGitArgs(&.{ "--refuse-signed", "fixture.git", "dest" });
    try std.testing.expect(opted_out.refuse_signed);
    const aborting = import_cli.exportArgv("fixture.git", opted_out.refuse_signed);
    try std.testing.expect(argvHas(&aborting, "--signed-commits=abort"));
    try std.testing.expect(argvHas(&aborting, "--signed-tags=abort"));
    try std.testing.expectEqual(stripping.len, aborting.len);
    for (stripping, aborting) |strip_token, abort_token| {
        if (std.mem.startsWith(u8, strip_token, "--signed-")) continue;
        try std.testing.expectEqualStrings(strip_token, abort_token);
    }

    // Same error shape as every other boolean flag: repeated is a usage error,
    // and the retired spelling is simply unknown.
    try std.testing.expectError(
        cli.ImportGitParseError.RepeatedOption,
        cli.parseImportGitArgs(&.{ "--refuse-signed", "--refuse-signed", "fixture.git", "dest" }),
    );
    try std.testing.expectError(
        cli.ImportGitParseError.UnknownOption,
        cli.parseImportGitArgs(&.{ "--drop-signatures", "fixture.git", "dest" }),
    );
}

test "import-git help exits zero and malformed argument forms exit usage without process launch" {
    const help = import_cli.dispatchForTest(&.{"--help"});
    try std.testing.expectEqual(cli.Exit.ok, help.code);
    try std.testing.expectEqualStrings("", help.err);
    try std.testing.expect(std.mem.startsWith(u8, help.out, "usage: zit import-git SOURCE DEST"));
    // Substring checks are what let the previous hand transcription rot two
    // options behind the real help, so pin every option the usage line owns.
    for ([_][]const u8{
        "--submanifest-k K | --no-submanifests",
        "--attribution",
        "--default-branch",
        "--refuse-signed",
        "--max-export-payload-bytes",
        "--quiet",
    }) |option| {
        try std.testing.expect(std.mem.indexOf(u8, help.out, option) != null);
    }

    const accepted_flat = import_cli.dispatchForTest(&.{ "--no-submanifests", "fixture.git", "dest" });
    try std.testing.expectEqual(cli.Exit.failure, accepted_flat.code);
    try std.testing.expectEqual(@as(usize, 0), accepted_flat.lifecycle.launched);
    try std.testing.expectEqualStrings("test dispatch requires scripted dependencies\n", accepted_flat.err);

    const accepted_max = import_cli.dispatchForTest(&.{ "--submanifest-k", "12", "fixture.git", "dest" });
    try std.testing.expectEqual(cli.Exit.failure, accepted_max.code);
    try std.testing.expectEqual(@as(usize, 0), accepted_max.lifecycle.launched);
    try std.testing.expectEqualStrings("test dispatch requires scripted dependencies\n", accepted_max.err);

    for ([_][]const []const u8{
        &.{ "--submanifest-k", "0", "fixture.git", "dest" },
        &.{ "--submanifest-k", "13", "fixture.git", "dest" },
        &.{ "--submanifest-k", "14", "fixture.git", "dest" },
        &.{ "--submanifest-k", "15", "fixture.git", "dest" },
        &.{ "--attribution", "committerish", "fixture.git", "dest" },
        // The conflicting pair exits `usage` in both orders and launches
        // nothing -- no process is spawned to discover an argument mistake.
        &.{ "--no-submanifests", "--submanifest-k", "5", "fixture.git", "dest" },
        &.{ "--submanifest-k", "5", "--no-submanifests", "fixture.git", "dest" },
        &.{ "--no-submanifests", "--no-submanifests", "fixture.git", "dest" },
        &.{"fixture.git"},
        &.{ "fixture.git", "dest", "extra" },
    }) |args| {
        const usage = import_cli.dispatchForTest(args);
        try std.testing.expectEqual(cli.Exit.usage, usage.code);
        try std.testing.expectEqual(@as(usize, 0), usage.lifecycle.launched);
        try std.testing.expectEqualStrings("", usage.out);
        try std.testing.expect(usage.err.len != 0);
    }
}

test "import-git rejects missing Git non-repository shallow and partial sources before export" {
    const preflight_failures = [_]import_cli.CommandScript.Preflight{
        .git_missing,
        .not_repository,
        .missing_objects,
        .shallow,
        .partial_promisor,
    };
    for (preflight_failures) |preflight| {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{ .preflight = preflight });
        const result = scripted(tmp, &script, "rejected", false, .real_filesystem);
        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expectEqual(@as(usize, 0), script.export_starts);
        try std.testing.expect(script.preflight_starts != 0);
        try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
        try expectNoPublication(std.testing.io, tmp, "rejected");
    }
}
test "import-git production preflight rejects absent non-repository missing-object shallow and partial sources" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var root_buf: [128]u8 = undefined;
    const root = try std.fmt.bufPrint(&root_buf, ".zig-cache/tmp/{s}", .{tmp.sub_path});
    var source_buf: [160]u8 = undefined;
    const source = try std.fmt.bufPrint(&source_buf, "{s}/source", .{root});

    // A non-existent -C path and an existing non-repository both execute real
    // ProductionGit query children and fail before an export can launch.
    try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "missing-git"));
    try tmp.dir.createDir(std.testing.io, "not-repository", .default_dir);
    var nonrepo_buf: [160]u8 = undefined;
    const nonrepo = try std.fmt.bufPrint(&nonrepo_buf, "{s}/not-repository", .{root});
    try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, nonrepo, "not-repository-dest"));
    try expectNoPublication(std.testing.io, tmp, "missing-git");
    try expectNoPublication(std.testing.io, tmp, "not-repository-dest");

    try runGit(std.testing.io, &.{ "git", "init", source });
    try runGit(std.testing.io, &.{ "git", "-C", source, "config", "user.name", "Zit Test" });
    try runGit(std.testing.io, &.{ "git", "-C", source, "config", "user.email", "[email protected]" });
    try runGit(std.testing.io, &.{ "git", "-C", source, "commit", "--allow-empty", "-m", "init" });

    try runGit(std.testing.io, &.{ "git", "-C", source, "config", "extensions.partialClone", "origin" });
    try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "partial"));
    try runGit(std.testing.io, &.{ "git", "-C", source, "config", "--unset", "extensions.partialClone" });

    try writeFixtureFile(std.testing.io, tmp.dir, "source/.git/shallow", "0000000000000000000000000000000000000000\n");
    try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "shallow"));
    try tmp.dir.deleteFile(std.testing.io, "source/.git/shallow");

    try tmp.dir.deleteTree(std.testing.io, "source/.git/objects");
    try std.testing.expectEqual(cli.Exit.failure, runProductionPreflight(tmp, source, "missing-objects"));
    try expectNoPublication(std.testing.io, tmp, "partial");
    try expectNoPublication(std.testing.io, tmp, "shallow");
    try expectNoPublication(std.testing.io, tmp, "missing-objects");
}

test "import-git records detached HEAD and refuses it unless default branch is explicit" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{ .preflight = .detached_head, .fast_export = valid_export });
    const rejected = scripted(tmp, &script, "detached", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.failure, rejected.code);
    try std.testing.expectEqual(@as(usize, 0), script.export_starts);
    try expectNoPublication(std.testing.io, tmp, "detached");

    script.default_branch = "main";
    const accepted = scripted(tmp, &script, "detached-with-branch", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, accepted.code);
    try std.testing.expect(script.detached_head_recorded);
}

test "import-git starts fast-export with the exact safe argv and disabled prompt and lazy fetch" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export });
    const result = scripted(tmp, &script, "argv", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, result.code);
    try std.testing.expectEqualStrings(
        "git -C fixture.git fast-export --all --show-original-ids --mark-tags --signed-commits=strip --signed-tags=strip --use-done-feature",
        script.exportCommandLine(),
    );
    try std.testing.expectEqualStrings("0", script.environment.get("GIT_TERMINAL_PROMPT").?);
    try std.testing.expectEqualStrings("1", script.environment.get("GIT_NO_LAZY_FETCH").?);
}

test "import-git drains real OS-pipe backpressure concurrently while stdout remains active" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    const result = try runProductionPipe(tmp, "flood", "success");

    try std.testing.expectEqual(cli.Exit.ok, result.code);
    try std.testing.expectEqual(@as(usize, 8 * 1024 * 1024), result.stderr_bytes_drained);
    try std.testing.expectEqual(@as(usize, 4096), result.retained_diagnostic_len);
    try std.testing.expect(result.err.len < 4096);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "imported 'fixture.git'") != null);
    try std.testing.expectEqual(@as(u8, 0), result.exit_code);
    try std.testing.expect(result.reaped);
    try std.testing.expect(result.child_gone);
}

test "import-git cancellation terminates a real child then joins and reaps its actual status once" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    const result = try runProductionPipe(tmp, "cancelled", "hold");

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(result.exit_code != 0);
    try std.testing.expect(result.err.len < 4096);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "Malformed") != null);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "GitExportFailed") == null);
    try std.testing.expect(result.reaped);
    try std.testing.expect(result.child_gone);
    try expectNoPublication(std.testing.io, tmp, "cancelled");
}

test "import-git non-Darwin/Linux fallback drains stable stderr ownership then reaps once" {
    if (builtin.os.tag == .macos or builtin.os.tag == .linux) return;
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    const result = try runProductionPipe(tmp, "fallback-cancelled", "hold");

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(result.stderr_bytes_drained != 0);
    try std.testing.expect(result.reaped);
    try std.testing.expectEqual(@as(usize, 1), result.reap_calls);
    try std.testing.expect(result.child_gone);
    try expectNoPublication(std.testing.io, tmp, "fallback-cancelled");
}

test "import-git rejects a nonzero completed export after draining and reaping it once" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    const result = try runProductionPipe(tmp, "nonzero-export", "nonzero");

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expectEqual(@as(u8, 23), result.exit_code);
    try std.testing.expect(result.reaped);
    try std.testing.expect(result.child_gone);
    try expectNoPublication(std.testing.io, tmp, "nonzero-export");
}
test "import-git prefers a nonzero truncated export status over its parser EOF" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    const result = try runProductionPipe(tmp, "signed-like-export", "truncated-nonzero");

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expectEqual(@as(u8, 23), result.exit_code);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "GitExportFailed") != null);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "fatal: signed commit rejected") != null);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "UnexpectedEof") == null);
    try std.testing.expect(result.reaped);
    try std.testing.expect(result.child_gone);
    try expectNoPublication(std.testing.io, tmp, "signed-like-export");
}

test "import-git verifies the frozen .zit and import map before the atomic rename" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export });
    const result = scripted(tmp, &script, "verified", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, result.code);
    try std.testing.expect(script.publication.store_reopened_for_verify);
    try std.testing.expect(script.publication.map_reopened_for_verify);
    try std.testing.expect(script.publication.verify_before_publish);
}

test "import-git competing destination at commit point never clobbers it and removes its temporary sibling" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{
        .preflight = .branch_head,
        .fast_export = valid_export,
        .competing_destination_at_publish = true,
    });
    const result = scripted(tmp, &script, "raced", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(script.competitor_destination_intact);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
    try std.testing.expect(!script.temporary_sibling_left);
}

test "import-git never publishes or deletes a replaced staging sibling" {
    const replacements = [_]import_cli.CommandScript.StagingReplacement{ .directory, .symlink };
    for (replacements) |replacement| {
        for (0..8) |_| {
            var tmp = std.testing.tmpDir(.{});
            defer tmp.cleanup();
            try tmp.dir.createDir(std.testing.io, "outside", .default_dir);
            try writeFixtureFile(std.testing.io, tmp.dir, "outside/replacement-sentinel", "survives\n");

            var script = import_cli.CommandScript.init(.{
                .preflight = .branch_head,
                .fast_export = valid_export,
                .replace_staging_at_publish = replacement,
            });
            const result = scripted(tmp, &script, "raced-staging", false, .real_filesystem);

            try std.testing.expectEqual(cli.Exit.failure, result.code);
            try std.testing.expect(std.mem.indexOf(u8, result.err, "StagingEntryReplaced") != null);
            try std.testing.expect(script.staging_replacement_observed);
            try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated);
            try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
            try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
            try std.testing.expect(!directoryExists(std.testing.io, tmp.dir, "raced-staging"));
            try std.testing.expect(!exists(std.testing.io, tmp.dir, ".zit-import-map.jsonl"));

            const staging_name = script.stagingReplacementName();
            try std.testing.expect(staging_name.len != 0);
            var outside = try tmp.dir.openDir(std.testing.io, "outside", .{ .follow_symlinks = false });
            defer outside.close(std.testing.io);
            try std.testing.expect(exists(std.testing.io, outside, "replacement-sentinel"));
            switch (replacement) {
                .directory => {
                    var preserved = try tmp.dir.openDir(std.testing.io, staging_name, .{ .follow_symlinks = false });
                    defer preserved.close(std.testing.io);
                    try std.testing.expect(exists(std.testing.io, preserved, "replacement-sentinel"));
                },
                .symlink => {
                    var target: [32]u8 = undefined;
                    const len = try tmp.dir.readLink(std.testing.io, staging_name, &target);
                    try std.testing.expectEqualStrings("outside", target[0..len]);
                },
            }
        }
    }
}

test "import-git default ceilings clear the retired 200k per-commit file wall" {
    const limits: import_cli.ImportLimits = .{};
    // The import leaves the arena budget at its default, so that default is
    // what an operator actually meets.
    const defaults: fast_export.Limits = .{ .max_line = 0, .max_data = 0, .max_path = 0 };
    const ceiling = fast_export.fileCommandCeiling(defaults);
    // The retired ceiling was 200,000 because that is the benchmark corpus
    // width. The derived one must clear it by orders of magnitude, so no
    // ordinary monorepo root commit can reach it.
    try std.testing.expect(ceiling > 100 * 200_000);
    try std.testing.expectEqual(
        defaults.max_command_arena_bytes / fast_export.min_retained_bytes_per_file_command,
        ceiling,
    );
    // The cumulative payload budget must clear a real repository: sqlite's
    // export stream is 12.2 GiB, and a default that refuses it is a default
    // that refuses ordinary work. It must also no longer move the ceiling.
    try std.testing.expect(limits.max_export_payload_bytes > 16 * @as(u64, 1024 * 1024 * 1024));
    var raised = defaults;
    raised.max_total_data = limits.max_export_payload_bytes;
    try std.testing.expectEqual(ceiling, fast_export.fileCommandCeiling(raised));
    try std.testing.expect(limits.max_export_events >= 400_000);
    try std.testing.expect(limits.max_retained_entries >= 6 * 400_000);
    try std.testing.expect(limits.max_retained_bytes >= 64 * 1024 * 1024);
}

test "import-git reader limits reject before event and data parsing allocations" {
    const event_limited = "feature done\nblob\nmark :1\ndata 1\nx\n";
    var event_reader = fast_export.Reader.init(std.testing.allocator, .fixed(event_limited), .{
        .max_line = 1024,
        .max_data = 1024,
        .max_path = 1024,
        .max_events = 0,
        .max_total_data = 1024,
    });
    defer event_reader.deinit();
    try std.testing.expectError(error.EventLimitExceeded, event_reader.next());

    const data_limited = "feature done\nblob\nmark :1\ndata 1\nx\n";
    var data_reader = fast_export.Reader.init(std.testing.allocator, .fixed(data_limited), .{
        .max_line = 1024,
        .max_data = 1024,
        .max_path = 1024,
        .max_events = 1,
        .max_total_data = 0,
    });
    defer data_reader.deinit();
    try std.testing.expectError(error.DataLimitExceeded, data_reader.next());
}
test "import-git enforces total import ceilings before publication" {
    // The two budgets are separate now, so the accepted value only has to
    // clear the stream's nine payload bytes.
    const one_command_budget = fast_export.min_retained_bytes_per_file_command;
    const accepted_limits: import_cli.ImportLimits = .{
        .max_export_events = 3,
        .max_export_payload_bytes = one_command_budget,
        // Bumped from 7 by the mirror classifier's tables. A blob now also
        // retains its content facts, and a commit event is authorized for the
        // reference, candidate and tags-grammar entries its own file commands
        // can add -- a per-event bound, so the headroom is the worst case for
        // this stream's single command rather than its actual growth of two.
        .max_retained_entries = 16,
        .max_retained_bytes = 1024,
    };
    const RejectedLimit = struct {
        limits: import_cli.ImportLimits,
        error_name: []const u8,
    };
    const rejected_limits = [_]RejectedLimit{
        .{
            .limits = .{
                .max_export_events = accepted_limits.max_export_events - 1,
                .max_export_payload_bytes = accepted_limits.max_export_payload_bytes,
                .max_retained_entries = accepted_limits.max_retained_entries,
            },
            .error_name = "ExportEventLimitExceeded",
        },
        .{
            // One byte below the stream's own payload. The blob's five bytes
            // admit; the commit message's four push the total past it, before
            // any file command is parsed.
            .limits = .{
                .max_export_events = accepted_limits.max_export_events,
                .max_export_payload_bytes = 8,
                .max_retained_entries = accepted_limits.max_retained_entries,
            },
            .error_name = "ExportPayloadLimitExceeded",
        },
        .{
            .limits = .{
                .max_export_events = accepted_limits.max_export_events,
                .max_export_payload_bytes = accepted_limits.max_export_payload_bytes,
                .max_retained_entries = accepted_limits.max_retained_entries - 1,
            },
            .error_name = "ImporterRetainedEntryLimitExceeded",
        },
        .{
            .limits = .{
                .max_export_events = accepted_limits.max_export_events,
                .max_export_payload_bytes = accepted_limits.max_export_payload_bytes,
                .max_retained_entries = accepted_limits.max_retained_entries,
                .max_retained_bytes = 0,
            },
            .error_name = "ImporterRetainedByteLimitExceeded",
        },
    };
    {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{
            .preflight = .branch_head,
            .fast_export = valid_export_with_oids,
            .limits = accepted_limits,
        });
        const result = scripted(tmp, &script, "at-limit", false, .real_filesystem);
        try std.testing.expectEqual(cli.Exit.ok, result.code);
        try std.testing.expectEqual(@as(usize, 0), result.lifecycle.terminated);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
    }

    for (rejected_limits, 0..) |rejected, index| {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{
            .preflight = .branch_head,
            .fast_export = valid_export_with_oids,
            .limits = rejected.limits,
        });
        var destination: [32]u8 = undefined;
        const destination_name = try std.fmt.bufPrint(&destination, "limit-{d}", .{index});
        const result = scripted(tmp, &script, destination_name, false, .real_filesystem);

        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expect(std.mem.indexOf(u8, result.err, rejected.error_name) != null);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
        try expectNoPublication(std.testing.io, tmp, destination_name);
    }
    {
        // One byte below one command's minimum retention derives a ceiling of
        // zero, so the stream's single `M` command is refused. The failure must
        // name the observed count, the ceiling, and the derivation. It must
        // NOT name a flag: this ceiling is structural, and the payload knob
        // that once raised it no longer moves it at all.
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{
            .preflight = .branch_head,
            .fast_export = valid_export_with_oids,
            .limits = .{
                .max_export_events = accepted_limits.max_export_events,
                .max_command_arena_bytes = one_command_budget - 1,
                .max_retained_entries = accepted_limits.max_retained_entries,
                .max_retained_bytes = accepted_limits.max_retained_bytes,
            },
        });
        const result = scripted(tmp, &script, "command-ceiling", false, .real_filesystem);
        try std.testing.expectEqual(cli.Exit.failure, result.code);

        var expected: [512]u8 = undefined;
        const diagnostic = try std.fmt.bufPrint(
            &expected,
            "import-git: import failed: ExportFileCommandLimitExceeded\n" ++
                "import-git: a commit exceeded the per-commit file-command ceiling: " ++
                "observed at least 1, ceiling 0, derived from the {d}-byte per-commit " ++
                "arena budget at {d} minimum retained bytes per command. This is a " ++
                "structural bound on one commit, not a total: no option raises it, " ++
                "because a commit at this width is beyond what the importer can hold whole\n",
            .{ one_command_budget - 1, one_command_budget },
        );
        try std.testing.expect(std.mem.indexOf(u8, result.err, diagnostic) != null);
        try std.testing.expect(std.mem.indexOf(u8, result.err, "--max-export-payload-bytes") == null);
        try expectNoPublication(std.testing.io, tmp, "command-ceiling");
    }
    {
        // The cumulative-payload refusal must name its numbers for the same
        // reason the file-command one does. `ExportPayloadLimitExceeded` alone
        // sent a real 32k-commit import 17 minutes downstream to learn a single
        // integer, and the integer it needs is not even the one it was refused
        // at: parsing stops at the overrun, so the observed total is a lower
        // bound on the stream's payload and the budget must clear the whole of
        // it. Saying "above 9" would name a value that fails again.
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{
            .preflight = .branch_head,
            .fast_export = valid_export_with_oids,
            .limits = .{
                .max_export_events = accepted_limits.max_export_events,
                .max_export_payload_bytes = 8,
                .max_retained_entries = accepted_limits.max_retained_entries,
                .max_retained_bytes = accepted_limits.max_retained_bytes,
            },
        });
        const result = scripted(tmp, &script, "payload-ceiling", false, .real_filesystem);
        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expect(std.mem.indexOf(
            u8,
            result.err,
            "import-git: import failed: ExportPayloadLimitExceeded\n" ++
                "import-git: the export stream's cumulative payload exceeded its budget: " ++
                "observed at least 9 bytes against a budget of 8 bytes; parsing stopped there, " ++
                "so the stream's full payload is larger than the observed total; re-run with " ++
                "--max-export-payload-bytes above the stream's full payload\n",
        ) != null);
        try expectNoPublication(std.testing.io, tmp, "payload-ceiling");
    }
    {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{
            .preflight = .branch_head,
            .fast_export = "feature done\nprogress first\nprogress second\n",
            .limits = .{ .max_export_events = 1 },
        });
        const result = scripted(tmp, &script, "progress-limit", false, .real_filesystem);
        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expect(std.mem.indexOf(u8, result.err, "ExportEventLimitExceeded") != null);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
        try expectNoPublication(std.testing.io, tmp, "progress-limit");
    }
}

test "import-git aborts every pre-rename writable-boundary failure without a partial destination" {
    const failures = [_]import_cli.PublicationHooks.FailurePoint{
        .before_flush_store,             .after_flush_store,
        .before_sync_store,              .after_sync_store,
        .before_close_store,             .after_close_store,
        .before_flush_map,               .after_flush_map,
        .before_sync_map,                .after_sync_map,
        .before_close_map,               .after_close_map,
        .capture_drop_set,               .create_packed_store,
        .before_pack,                    .after_pack,
        .before_sync_packed_store,       .after_sync_packed_store,
        .before_close_packed_store,      .after_close_packed_store,
        .verify_raw_store,               .before_discard_raw_generation,
        .after_discard_raw_generation,   .before_sync_temporary_directory,
        .after_sync_temporary_directory, .before_rename_no_replace,
    };
    for (failures) |failure| {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export });
        const result = scripted(tmp, &script, "boundary-failed", false, .{ .fail_at = failure });
        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
        try expectNoPublication(std.testing.io, tmp, "boundary-failed");
    }
}

test "import-git reports parent sync durability uncertainty after publish while retaining the verified destination" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export });
    const result = scripted(tmp, &script, "durability-uncertain", false, .{ .fail_at = .sync_parent_after_publish });
    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(result.published_durability_uncertain);
    var destination = try tmp.dir.openDir(std.testing.io, "durability-uncertain", .{});
    defer destination.close(std.testing.io);
    try std.testing.expect(exists(std.testing.io, destination, ".zit"));
    try std.testing.expect(exists(std.testing.io, destination, ".zit-import-map.jsonl"));
}

test "import-git start session failure after stderr thread closes pipes and reaps exactly once" {
    var session = import_cli.ProductionGit.initForTest(
        std.testing.allocator,
        std.testing.io,
        &.{ helper_options.git_import_pipe_helper, "hold" },
    );
    defer session.deinit();
    session.test_startup_failure = .after_stderr_thread;

    const dependencies = session.dependencies(.real_filesystem);
    try std.testing.expectError(
        error.InjectedStartFailure,
        dependencies.git.vtable.start_export(dependencies.git.context, "fixture.git"),
    );
    try std.testing.expect(session.child == null);
    try std.testing.expect(session.stderr_thread == null);
    try std.testing.expect(session.stderr_file == null);
    try std.testing.expect(session.reaped);
    try std.testing.expectEqual(@as(usize, 1), session.reap_calls);
}

test "import-git rejects a replacement destination after rename without deleting it" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var script = import_cli.CommandScript.init(.{
        .preflight = .branch_head,
        .fast_export = valid_export,
        .replace_destination_after_rename = true,
    });
    const result = scripted(tmp, &script, "replacement-after-rename", false, .real_filesystem);

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "PublishedDestinationReplaced") != null);
    try std.testing.expect(script.competitor_destination_intact);
    var replacement = try tmp.dir.openDir(std.testing.io, "replacement-after-rename", .{
        .follow_symlinks = false,
    });
    defer replacement.close(std.testing.io);
    try std.testing.expect(exists(std.testing.io, replacement, "replacement-sentinel"));
    try std.testing.expect(!script.temporary_sibling_left);
}

test "import-git reaps after termination failure and reports the lifecycle error" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var git: ScriptedGit = .{
        .fast_export = malformed_export,
        .lifecycle_fault = .terminate,
    };
    const result = runFixture(tmp, "fixture.git", "termination-failure", false, &git, .real_filesystem);

    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "InjectedLifecycleFailure") != null);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.terminated);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.stderr_drained);
    try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
    try expectNoPublication(std.testing.io, tmp, "termination-failure");
}

test "import-git accounts the peeled target retained for marked tags at the exact byte boundary" {
    const exact_limit = try retainedBytesForExport(marked_tag_export);
    var exact_tmp = std.testing.tmpDir(.{});
    defer exact_tmp.cleanup();
    var exact_git: ScriptedGit = .{
        .fast_export = marked_tag_export,
        .limits = .{ .max_retained_bytes = exact_limit },
    };
    const exact = runFixture(exact_tmp, "fixture.git", "marked-tag-exact", false, &exact_git, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, exact.code);

    var exceeded_tmp = std.testing.tmpDir(.{});
    defer exceeded_tmp.cleanup();
    var exceeded_git: ScriptedGit = .{
        .fast_export = marked_tag_export,
        .limits = .{ .max_retained_bytes = exact_limit - 1 },
    };
    const exceeded = runFixture(exceeded_tmp, "fixture.git", "marked-tag-exceeded", false, &exceeded_git, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.failure, exceeded.code);
    try std.testing.expect(std.mem.indexOf(u8, exceeded.err, "ImporterRetainedByteLimitExceeded") != null);
    try expectNoPublication(std.testing.io, exceeded_tmp, "marked-tag-exceeded");
}

/// A fast-export whose single commit lands `count` distinct paths. Wide enough,
/// at the k the tree-mode side of the comparison pins, that tree emission
/// really stores SUBMANIFEST artifacts -- without that, "the flat import stored
/// none" would be true of both modes and would prove nothing.
fn wideExport(gpa: std.mem.Allocator, count: usize) ![]u8 {
    var out: std.ArrayList(u8) = .empty;
    errdefer out.deinit(gpa);
    try out.appendSlice(
        gpa,
        "feature done\n" ++
            "blob\n" ++
            "mark :1\n" ++
            "data 5\n" ++
            "hello\n" ++
            "commit refs/heads/main\n" ++
            "mark :2\n" ++
            "author A <[email protected]> 0 +0000\n" ++
            "committer A <[email protected]> 0 +0000\n" ++
            "data 4\n" ++
            "init\n",
    );
    for (0..count) |index| {
        const line = try std.fmt.allocPrint(gpa, "M 100644 :1 file{d:0>6}.txt\n", .{index});
        defer gpa.free(line);
        try out.appendSlice(gpa, line);
    }
    try out.appendSlice(gpa, "\n" ++ "done\n");
    return out.toOwnedSlice(gpa);
}

const ImportedSubmanifestState = struct {
    enable_submanifests: bool,
    submanifest_k: u8,
    seen_submanifest: bool,
    /// Whether ANY artifact in the produced store is a SUBMANIFEST. Read off
    /// the store itself rather than off the config frame, so the two statements
    /// can disagree and be caught doing it.
    submanifests_stored: bool,
    checkin_paths: usize,
};

/// Everything this feature claims about a produced repository, read back from
/// the repository: the four persisted sub-manifest settings, whether the store
/// holds a SUBMANIFEST artifact, and the flattened path count of the one
/// check-in. The path count is what lets the caller assert that the two modes
/// imported the SAME history.
fn readImportedSubmanifestState(io: std.Io, destination: std.Io.Dir) !ImportedSubmanifestState {
    const gpa = std.testing.allocator;
    var file = try destination.openFile(io, ".zit", .{});
    defer file.close(io);
    const stat = try file.stat(io);
    var file_storage = storage.FileStorage.init(io, file, stat.size);
    var log = try logstore.LogStore.openReadOnly(gpa, file_storage.storageFile());
    defer log.deinit(gpa);

    var owned = (try log.readConfig(gpa)) orelse return error.TestUnexpectedResult;
    defer owned.deinit(gpa);
    try std.testing.expect(!owned.config.submanifest_auto_retune);

    const rids = try log.ridsInPersistedOrder(gpa);
    defer gpa.free(rids);
    var submanifests: usize = 0;
    var checkins: usize = 0;
    var checkin_paths: usize = 0;
    for (rids) |rid| {
        const bytes = (try log.content(gpa, rid)) orelse continue;
        defer gpa.free(bytes);
        // A blob is not parseable as an artifact; skipping is the whole
        // classification here, so it must not be an error.
        var manifest = artifact.parse(gpa, bytes) catch continue;
        defer manifest.deinit();
        switch (manifest.kind) {
            .submanifest => submanifests += 1,
            .manifest => {
                checkins += 1;
                checkin_paths = if (manifest.s.len != 0) manifest.s.len else manifest.f.len;
            },
            else => {},
        }
    }
    try std.testing.expectEqual(@as(usize, 1), checkins);
    return .{
        .enable_submanifests = owned.config.enable_submanifests,
        .submanifest_k = owned.config.submanifest_k,
        .seen_submanifest = owned.config.seen_submanifest,
        .submanifests_stored = submanifests != 0,
        .checkin_paths = checkin_paths,
    };
}

/// Every check-in manifest in the store carries only F cards, and no artifact
/// in it is a SUBMANIFEST. This is the store-side reading of §8.1's "MUST NOT
/// emit an S card"; it consults no flag and no config field.
fn expectNoSCardsAnywhere(io: std.Io, destination: std.Io.Dir) !void {
    const gpa = std.testing.allocator;
    var file = try destination.openFile(io, ".zit", .{});
    defer file.close(io);
    const stat = try file.stat(io);
    var file_storage = storage.FileStorage.init(io, file, stat.size);
    var log = try logstore.LogStore.openReadOnly(gpa, file_storage.storageFile());
    defer log.deinit(gpa);

    const rids = try log.ridsInPersistedOrder(gpa);
    defer gpa.free(rids);
    for (rids) |rid| {
        const bytes = (try log.content(gpa, rid)) orelse continue;
        defer gpa.free(bytes);
        var manifest = artifact.parse(gpa, bytes) catch continue;
        defer manifest.deinit();
        try std.testing.expect(manifest.kind != .submanifest);
        try std.testing.expectEqual(@as(usize, 0), manifest.s.len);
    }
}

test "import-git --no-submanifests leaves the destination at the spec-default config with no S cards" {
    const gpa = std.testing.allocator;
    // 96 paths at k=1 is comfortably past the point the canonical partition
    // yields more than one group, which the tree-mode half below proves by
    // actually storing SUBMANIFEST artifacts from this exact stream.
    const export_stream = try wideExport(gpa, 96);
    defer gpa.free(export_stream);

    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var flat_git: ScriptedGit = .{ .fast_export = export_stream };
    const flat = runFixtureShaped(tmp, "fixture.git", "flat", false, &flat_git, .real_filesystem, .flat);
    try std.testing.expectEqual(cli.Exit.ok, flat.code);
    // "Do the default thing" reports nothing about sub-manifests.
    try std.testing.expect(std.mem.indexOf(u8, flat.err, "sub-manifest") == null);

    var tree_git: ScriptedGit = .{ .fast_export = export_stream };
    const tree = runFixtureShaped(tmp, "fixture.git", "tree", false, &tree_git, .real_filesystem, .{ .tree = 1 });
    try std.testing.expectEqual(cli.Exit.ok, tree.code);

    // The other side of the same gate. Flat's silence above only means
    // something if an unpinned tree import does report its analyzed k, so pin
    // that here too -- otherwise deleting the report entirely would leave both
    // halves green.
    var analyzed_git: ScriptedGit = .{ .fast_export = export_stream, .analyzed_submanifest_k = 3 };
    const analyzed = runFixtureShaped(tmp, "fixture.git", "analyzed", false, &analyzed_git, .real_filesystem, .{ .tree = null });
    try std.testing.expectEqual(cli.Exit.ok, analyzed.code);
    try std.testing.expect(std.mem.indexOf(u8, analyzed.err, "analyzed sub-manifest k=3") != null);

    var flat_dir = try tmp.dir.openDir(std.testing.io, "flat", .{});
    defer flat_dir.close(std.testing.io);
    var tree_dir = try tmp.dir.openDir(std.testing.io, "tree", .{});
    defer tree_dir.close(std.testing.io);

    const flat_state = try readImportedSubmanifestState(std.testing.io, flat_dir);
    const tree_state = try readImportedSubmanifestState(std.testing.io, tree_dir);

    // The tree half is asserted first because it is what gives the flat half
    // its meaning: this stream at this k really does produce sub-manifests, and
    // today's tree-mode config frame is pinned here as the contrast.
    try std.testing.expect(tree_state.submanifests_stored);
    try std.testing.expect(tree_state.enable_submanifests);
    try std.testing.expectEqual(@as(u8, 1), tree_state.submanifest_k);
    try std.testing.expect(tree_state.seen_submanifest);

    // The flat half: identical to a fresh `zit init` as far as sub-manifest
    // behavior goes. `submanifest_k = 0` is "not pinned", not "k of zero".
    try std.testing.expect(!flat_state.submanifests_stored);
    try std.testing.expect(!flat_state.enable_submanifests);
    try std.testing.expectEqual(@as(u8, 0), flat_state.submanifest_k);
    try std.testing.expect(!flat_state.seen_submanifest);
    try expectNoSCardsAnywhere(std.testing.io, flat_dir);

    // ... and it imported the same history: every path landed, as F cards.
    try std.testing.expectEqual(@as(usize, 96), flat_state.checkin_paths);
    try std.testing.expect(tree_state.checkin_paths < flat_state.checkin_paths);
}

// ---------------------------------------------------------------------------
// Second generation: every successful import publishes a PACKED store.
//
// The transaction runs `writing_raw -> raw_frozen -> packing -> packed_frozen
// -> verified -> published`. Generation 1 is written under its own name and is
// never the thing that ships: it is frozen, packed into generation 2 through
// the classifier's exact candidate-only drop set, read once more by
// verification, and then deleted from staging before the directory is synced
// and renamed. `DEST/.zit` is generation 2 and nothing else.
// ---------------------------------------------------------------------------

// The two generations must be distinct FILES, not one file rewritten. A single
// name would make "pack generation 1 into generation 2" a truncating in-place
// rewrite with no recoverable source, which is the shape this transaction
// exists to avoid.
test "import-git second generation uses a file distinct from the raw generation" {
    try std.testing.expect(!std.mem.eql(
        u8,
        import_cli.raw_generation_name,
        import_cli.published_generation_name,
    ));
    try std.testing.expectEqualStrings(".zit", import_cli.published_generation_name);
}

test "import-git second generation advances its publication stages one way only" {
    const io = std.testing.io;
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var bundle = try import_cli.PublicationBundle.create(std.testing.allocator, io, tmp.dir, "one-way");
    defer bundle.abort();

    // Skipping ahead is rejected...
    try std.testing.expectError(error.PublicationStageOutOfOrder, bundle.advance(.packing));
    try std.testing.expectEqual(import_cli.PublicationBundle.Stage.writing_raw, bundle.stage);
    // ...one step at a time is accepted...
    try bundle.advance(.raw_frozen);
    try bundle.advance(.packing);
    // ...repeating a stage is rejected...
    try std.testing.expectError(error.PublicationStageOutOfOrder, bundle.advance(.packing));
    // ...and so is going back.
    try std.testing.expectError(error.PublicationStageOutOfOrder, bundle.advance(.raw_frozen));
    try std.testing.expectEqual(import_cli.PublicationBundle.Stage.packing, bundle.stage);
}

test "import-git second generation publish rejects every state earlier than verified" {
    const io = std.testing.io;
    const early = [_]import_cli.PublicationBundle.Stage{
        .writing_raw,
        .raw_frozen,
        .packing,
        .packed_frozen,
    };
    for (early) |stage| {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var bundle = try import_cli.PublicationBundle.create(std.testing.allocator, io, tmp.dir, "too-early");
        defer bundle.abort();
        bundle.stage = stage;
        try std.testing.expectError(
            error.PublicationNotVerified,
            bundle.publish("too-early", .real_filesystem),
        );
        try std.testing.expect(!directoryExists(io, tmp.dir, "too-early"));
    }
}

test "import-git second generation publishes the packed generation and no raw generation" {
    const io = std.testing.io;
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export });
    const result = scripted(tmp, &script, "second-generation", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, result.code);

    var destination = try tmp.dir.openDir(io, "second-generation", .{});
    defer destination.close(io);
    try std.testing.expect(exists(io, destination, import_cli.published_generation_name));
    // The rename ships the whole staging directory, so a surviving generation 1
    // would land in DEST. It must be gone before the commit point.
    try std.testing.expect(!exists(io, destination, import_cli.raw_generation_name));
    try std.testing.expect(exists(io, destination, ".zit-import-map.jsonl"));
    try std.testing.expect(exists(io, destination, "refs"));

    // Stage ordering, observed rather than asserted from the source.
    try std.testing.expect(script.publication.raw_frozen_before_packing);
    try std.testing.expect(script.publication.packed_generation_created);
    try std.testing.expect(script.publication.packed_before_verify);
    try std.testing.expect(script.publication.both_generations_present_before_discard);
    try std.testing.expect(script.publication.raw_generation_discarded);
    try std.testing.expect(script.publication.discard_before_rename);
    try std.testing.expect(script.publication.verify_before_publish);

    // The published store is a complete log in its own right.
    var store_file = try destination.openFile(io, import_cli.published_generation_name, .{});
    defer store_file.close(io);
    const stat = try store_file.stat(io);
    var file_storage = storage.FileStorage.init(io, store_file, stat.size);
    var log = try logstore.LogStore.openReadOnly(std.testing.allocator, file_storage.storageFile());
    defer log.deinit(std.testing.allocator);
    try std.testing.expect(log.entryCount() != 0);
}

// The pack report is the only place an operator learns that publication is
// packed at all, and the only externally visible statement of how many mirror
// artifacts the classifier authorized dropping. A stream with no Fossil mirror
// pair must report zero -- fail-closed, stated out loud.
test "import-git second generation reports its pack census and drops nothing without a mirror pair" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var git: ScriptedGit = .{ .fast_export = valid_export };
    const result = runFixture(tmp, "fixture.git", "census", false, &git, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.ok, result.code);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "import-git: packed ") != null);
    try std.testing.expect(std.mem.indexOf(u8, result.err, "dropped 0 mirror artifacts") != null);
}

test "import-git second generation aborts every packing-stage failure without a partial destination" {
    const failures = [_]import_cli.PublicationHooks.FailurePoint{
        .capture_drop_set,             .create_packed_store,
        .before_pack,                  .after_pack,
        .before_sync_packed_store,     .after_sync_packed_store,
        .before_close_packed_store,    .after_close_packed_store,
        .verify_raw_store,             .before_discard_raw_generation,
        .after_discard_raw_generation,
    };
    for (failures) |failure| {
        var tmp = std.testing.tmpDir(.{});
        defer tmp.cleanup();
        var script = import_cli.CommandScript.init(.{ .preflight = .branch_head, .fast_export = valid_export });
        const result = scripted(tmp, &script, "packing-failed", false, .{ .fail_at = failure });
        try std.testing.expectEqual(cli.Exit.failure, result.code);
        try std.testing.expectEqual(@as(usize, 1), result.lifecycle.reaped);
        // DEST absent, no map beside it, and no temporary sibling -- at every
        // boundary the second generation added, not just the ones that predate
        // packing.
        try expectNoPublication(std.testing.io, tmp, "packing-failed");
    }
}

// A competitor that appears at the commit point must survive untouched even
// though the transaction now has two generations to unwind, and the staging
// sibling holding BOTH of them must still be removed.
test "import-git second generation never clobbers a competing destination and leaves no sibling" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{
        .preflight = .branch_head,
        .fast_export = valid_export,
        .competing_destination_at_publish = true,
    });
    const result = scripted(tmp, &script, "packed-raced", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.failure, result.code);
    try std.testing.expect(script.competitor_destination_intact);
    try std.testing.expect(!script.temporary_sibling_left);
    // The competitor is a bare directory the script created: it must still be
    // bare, with neither generation written into it.
    var competitor = try tmp.dir.openDir(std.testing.io, "packed-raced", .{});
    defer competitor.close(std.testing.io);
    try std.testing.expect(!exists(std.testing.io, competitor, import_cli.published_generation_name));
    try std.testing.expect(!exists(std.testing.io, competitor, import_cli.raw_generation_name));
}

// The manifest check is what makes "only generation 2 is published" a property
// of the DIRECTORY at the instant it is renamed, rather than a property of the
// statement order that got it there. Stand in for a future edit that writes an
// intermediate file and forgets to remove it.
test "import-git second generation refuses to publish staging holding anything unmanifested" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();
    var script = import_cli.CommandScript.init(.{
        .preflight = .branch_head,
        .fast_export = valid_export,
        .stray_staging_file_after_discard = true,
    });
    const result = scripted(tmp, &script, "stray", false, .real_filesystem);
    try std.testing.expectEqual(cli.Exit.failure, result.code);
    // The stray arrives AFTER the discard, so the transaction was otherwise
    // complete: only the manifest check stands between it and the rename.
    try std.testing.expect(script.publication.raw_generation_discarded);
    try expectNoPublication(std.testing.io, tmp, "stray");
}

// `.verified` is both the pre-discard and the post-discard stage, so the stage
// alone cannot say whether generation 1 left staging. The commit point checks
// the fact directly.
test "import-git second generation publish rejects a bundle still holding the raw generation" {
    const io = std.testing.io;
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    var bundle = try import_cli.PublicationBundle.create(std.testing.allocator, io, tmp.dir, "raw-held");
    defer bundle.abort();
    bundle.stage = .verified;
    bundle.raw_present = true;
    try std.testing.expectError(
        error.PublicationRawGenerationPresent,
        bundle.publish("raw-held", .real_filesystem),
    );
    try std.testing.expect(!directoryExists(io, tmp.dir, "raw-held"));

    // The gate is the raw generation and nothing else: once it is gone, the
    // same bundle at the same stage publishes.
    bundle.raw_present = false;
    try bundle.publish("raw-held", .real_filesystem);
    try std.testing.expect(directoryExists(io, tmp.dir, "raw-held"));
}