// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Full runs end to end, at no cost: this binary as crafter-review on a // scratch repository, and a claude stand-in (/bin/sh) that answers every pass // from what the driver hands it. A run SIGKILLed in its last gate, then the // next one resuming its first gate from the checkpoint and salvaging the // outputs of its last; a run with nothing left to buy; a FIX FIRST; a stop by // `crafter-review stop`'s file and by SIGTERM; a stop between two gates. No // pass outlives any of them. #include #include import std; import Crafter.Review; namespace fs = std::filesystem; using namespace Crafter::Review; namespace { std::int32_t Failures = 0; void Check(std::string_view name, bool cond, std::string_view detail = {}) { std::println("{}{}{}", cond ? " ok " : " FAIL ", name, !cond && !detail.empty() ? std::format(" ({})", detail) : std::string()); if (!cond) ++Failures; } void Write(const fs::path& path, std::string_view text) { fs::create_directories(path.parent_path()); std::ofstream(path, std::ios::binary) << text; } std::string Read(const fs::path& path) { std::ifstream in(path, std::ios::binary); return {std::istreambuf_iterator(in), std::istreambuf_iterator()}; } std::vector Lines(const fs::path& path) { const std::string text = Read(path); return text.empty() ? std::vector() : Dyn::Split(Dyn::RStrip(text, "\n"), "\n"); } // claude for the runs: the lens from its system file, the units from its // prompt, every unit clear. Each pass adds a line to $FAKE_DIR/calls (its // pid, its lens, its units). FAKE_FIX: a lens:unit that comes back a // defect; FAKE_SLEEP: seconds a pass waits before it answers - every pass, // or the one FAKE_SLEEP_ON names. It speaks first, so the warm-up lets // the passes behind it go. constexpr std::string_view FakeClaude = R"sh(#!/bin/sh set -f [ "$1" = --version ] && { echo '2.1.314 (Claude Code)'; exit 0; } sys= model= sid= prev= for a in "$@"; do case $prev in --append-system-prompt-file) sys=$a ;; --model) model=$a ;; --session-id) sid=$a ;; esac prev=$a done lens=$(sed -n 's/^# Lens: //p' "$sys" | head -n 1) ids=$(sed -n -e 's/^SCOPE - BLOCK PASS, unit "\([^"]*\)".*/\1/p' -e 's/^SCOPE - SLOT PASS, unit "\([^"]*\)".*/\1/p' -e 's/^- \(whole:[a-z]*\) \[.*/\1/p') echo "$$ $lens" $ids >> "$FAKE_DIR/calls" echo "{\"type\":\"system\",\"subtype\":\"init\",\"session_id\":\"$sid\",\"model\":\"$model\",\"mcp_servers\":[],\"tools\":[\"Read\"]}" echo '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"reading"}]}}' pause=$FAKE_SLEEP if [ -n "$FAKE_SLEEP_ON" ]; then pause= for id in $ids; do [ "$lens:$id" = "$FAKE_SLEEP_ON" ] && pause=$FAKE_SLEEP; done fi if [ -n "$pause" ]; then sleep "$pause" & echo "$!" >> "$FAKE_DIR/sleeps" wait "$!" fi verdict=SEND-READY findings= coverage= summary= for id in $ids; do v=clear if [ "$lens:$id" = "$FAKE_FIX" ]; then v=defect verdict='FIX FIRST' findings="{\"finding\":\"planted\",\"evidence\":\"hello.py:2\",\"proposedAction\":\"fix it\",\"items\":[\"$id\"]}" fi coverage="$coverage${coverage:+,}{\"item\":\"$id\",\"verdict\":\"$v\",\"basis\":\"read at HEAD\"}" case $id in whole:*) summary=',"summary":"the approach holds"' ;; esac done echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\":\"ok\",\"structured_output\":{\"verdict\":\"$verdict\",\"findings\":[$findings],\"notes\":[],\"coverage\":[$coverage]$summary},\"usage\":{\"input_tokens\":1,\"output_tokens\":1},\"total_cost_usd\":0.01}" )sh"; // The environment of every child: git with no user config (the user's // signs commits), this checkout as the home, a board of the test's own, // and none of the review or seat variables of the shell the test runs in. std::map Env; Process::Captured Git(const fs::path& repo, std::vector args) { args.insert(args.begin(), {"git", "-C", repo.string()}); return Process::Run(args, std::nullopt, std::chrono::seconds(60), {}, Env); } std::map With(std::map env, const std::map& changes) { for (const auto& [k, v] : changes) env[k] = v; return env; } // The stand-in's processes and their sleeps that still run. std::vector Leftovers(const fs::path& fakeDir) { std::vector alive; auto look = [&](const fs::path& list, std::string_view marker) { for (const std::string& line : Lines(list)) { const std::string pid = line.substr(0, line.find(' ')); const std::string cmdline = Read(std::format("/proc/{}/cmdline", pid)); if (cmdline.contains(marker)) alive.push_back(std::format("{}: {}", pid, Dyn::Replace(cmdline, std::string(1, '\0'), " "))); } }; look(fakeDir / "calls", (fakeDir / "claude").string()); look(fakeDir / "sleeps", "sleep"); return alive; } // Leftovers once the supervisors have had their moment. std::vector LeftoversAfter(const fs::path& fakeDir) { for (std::int32_t i = 0; i < 100; ++i) { if (Leftovers(fakeDir).empty()) break; std::this_thread::sleep_for(std::chrono::milliseconds(50)); } return Leftovers(fakeDir); } bool WaitFor(const std::function& ready, std::chrono::seconds limit) { const auto end = std::chrono::steady_clock::now() + limit; while (std::chrono::steady_clock::now() < end) { if (ready()) return true; std::this_thread::sleep_for(std::chrono::milliseconds(50)); } return ready(); } // The dump of the highest run number on this hash. Json LatestDump(const fs::path& gateDir, std::string_view h8) { const std::string prefix = std::format("commits-{}-run-", h8); std::int64_t best = 0; fs::path found; for (const fs::directory_entry& entry : fs::directory_iterator(gateDir)) { const std::string name = entry.path().filename().string(); if (!name.starts_with(prefix) || !name.ends_with(".json")) continue; const std::string number = name.substr(prefix.size(), name.size() - prefix.size() - 5); if (number.empty() || !std::ranges::all_of(number, [](char c) { return c >= '0' && c <= '9'; })) continue; if (const std::int64_t n = std::stoll(number); n > best) { best = n; found = entry.path(); } } return found.empty() ? Json() : Dyn::LoadJsonFile(found); } // The run number in a dump's own name. std::int64_t RunNumber(const Json& dump) { if (!dump.is_object()) return 0; const std::string path = Dyn::Str(dump.at("dumpPath")); const std::size_t at = path.rfind("-run-"); return at == std::string::npos ? 0 : std::stoll(path.substr(at + 5)); } std::int64_t Count(const Json& records, std::string_view key, const Json& value) { return std::ranges::count_if(records, [&](const Json& r) { return Dyn::Get(r, key) == value; }); } std::string Text(const Json& value) { return value.is_string() ? value.get() : value.dump(); } std::int32_t Checks() { std::println("runs: a full run on a claude stand-in - killed and salvaged, resumed, nothing left to buy, FIX FIRST, stopped by the stop file, by SIGTERM and between gates"); Platform::SetMode(Platform::Mode::Native); const fs::path checkout = fs::current_path(); SetHome(checkout); const fs::path base = fs::temp_directory_path() / std::format("crafter-review-runs-{}", Platform::PublishedPid()); fs::remove_all(base); const fs::path repo = base / "repo"; const fs::path fakeDir = base / "fake"; const fs::path fake = fakeDir / "claude"; const fs::path scratchRoot = base / "scratch"; Write(fake, FakeClaude); fs::permissions(fake, fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec | fs::perms::others_read | fs::perms::others_exec); for (const auto& [k, v] : Platform::Environment()) { const bool review = k.starts_with("REVIEW_GATES_") || k.starts_with("CRAFTER_REVIEW_") || k == "SEATS_ENV_FILE"; const bool seat = k.starts_with("CLAUDE_CODE_OAUTH_TOKEN") || k.starts_with("ANTHROPIC_API_KEY") || k.starts_with("OPENROUTER_KEY") || k.starts_with("FAKE_"); if (!review && !seat) Env[k] = v; } // No seats file of the machine's own: XDG_CONFIG_HOME natively, and // HOST_XDG_CONFIG_HOME, which the Flatpak's Platform::Init copies // over it - a directory of this run's own, which nothing creates. const std::string noConfig = (base / "no-config").string(); Env = With(Env, {{"XDG_CONFIG_HOME", noConfig}, {"HOST_XDG_CONFIG_HOME", noConfig}, {"GIT_CONFIG_GLOBAL", "/dev/null"}, {"GIT_CONFIG_SYSTEM", "/dev/null"}, {"GIT_AUTHOR_NAME", "Me"}, {"GIT_AUTHOR_EMAIL", "me@example.com"}, {"GIT_COMMITTER_NAME", "Me"}, {"GIT_COMMITTER_EMAIL", "me@example.com"}, {"CRAFTER_REVIEW_HOME", checkout.string()}, {"REVIEW_GATES_BOARD", (base / "board.json").string()}, {"FAKE_DIR", fakeDir.string()}}); // A repository with one commit to review, the three static code lenses // on (every lens is off until a config turns it on; the functional // lens needs a running application, the commit-truth lens gets the // last scenario). fs::create_directories(repo); Git(repo, {"init", "-q", "-b", "main"}); Git(repo, {"config", "user.email", "me@example.com"}); Git(repo, {"config", "user.name", "Me"}); Write(repo / ".gitignore", ".review-gates/push/\n.review-gates/trees/\n"); Write(repo / ".review-gates" / "config.json", R"({"modes": {"commits": {"lenses": ["repo-better-solution", "repo-bug-prover", "repo-security"]}}})"); Write(repo / "README.md", "a greeter\n"); Git(repo, {"add", "-A"}); Git(repo, {"commit", "-q", "-m", "base"}); const std::string baseSha = Dyn::Strip(Git(repo, {"rev-parse", "HEAD"}).out); Write(repo / "hello.py", "def hello(name):\n return f'hello {name}'\n"); Git(repo, {"add", "hello.py"}); Git(repo, {"commit", "-q", "-m", "add hello\n\nThe greeting the README promises."}); const Process::Captured frozen = Process::Run({Platform::SelfExe().string(), "freeze", "--slug", "x", "--base", baseSha}, repo, std::chrono::seconds(60), {}, Env); const fs::path artifact = repo / ".review-gates" / "push" / "x"; const fs::path gateDir = artifact / "gate-state"; Json freezeJson; try { freezeJson = Dyn::LoadJsonFile(artifact / "freeze.json"); } catch (const std::exception&) { Check("freeze: the scratch range freezes", false, frozen.out + frozen.err); return 1; } const std::string hash = freezeJson.at("hash").get(); const std::string h8 = hash.substr(0, 8); auto runArgs = [&](std::vector extra) { std::vector args = {Platform::SelfExe().string(), "run", "--artifact", ".review-gates/push/x", "--hash", hash, "--base", baseSha, "--trees", ".", "--dispositions", ".review-gates/push/x/gate-state/dispositions.json", "--date", Text(freezeJson.at("date")), "--claude-bin", fake.string(), "--scratch", scratchRoot.string(), "--seats", "local", "--no-probe", "--reads", "1"}; std::ranges::move(extra, std::back_inserter(args)); return args; }; auto run = [&](std::vector extra, const std::map& env = {}) { return Process::Run(runArgs(std::move(extra)), repo, std::chrono::seconds(60), {}, With(Env, env)); }; auto start = [&](std::vector extra, const std::map& env, std::string_view name) { return Process::Start({.argv = runArgs(std::move(extra)), .environment = With(Env, env), .cwd = repo, .input = {.kind = Process::Stream::Kind::Null}, .output = {.kind = Process::Stream::Kind::File, .file = base / std::format("{}.out", name)}, .error = {.kind = Process::Stream::Kind::File, .file = base / std::format("{}.err", name)}}); }; auto calls = [&] { return static_cast(Lines(fakeDir / "calls").size()); }; // The plan, from a dry run: what every full run below buys. const Process::Captured dry = run({"--dry-run"}); Json plan; try { plan = Dyn::LoadJsonFile(scratchRoot / std::format("{}-run-1-dry", h8) / "dry-run.json"); } catch (const std::exception&) { } const Json& planned = plan.is_object() ? plan.at("processes") : Json::array(); const auto all = static_cast(planned.size()); const std::int64_t gate1 = Count(planned, "lens", "repo-better-solution"); std::string securityUnit; std::string proverUnit; for (const Json& p : planned) { if (p.at("kind") != "block") continue; if (securityUnit.empty() && p.at("lens") == "repo-security") securityUnit = Text(p.at("unit")); if (proverUnit.empty() && p.at("lens") == "repo-bug-prover") proverUnit = Text(p.at("unit")); } Check("dry run: the range plans the approach lens's whole pass and blocks, then the bug-prover's and the security lens's blocks - and no pass reaches claude", dry.returnCode == 2 && all > gate1 && gate1 > 1 && !securityUnit.empty() && !proverUnit.empty() && calls() == 0 && Count(planned, "lens", "repo-functional") == 0, dry.out.substr(dry.out.size() > 2000 ? dry.out.size() - 2000 : 0) + dry.err); // Dry runs with no full run between them share one dir: the next // drops the last one's prompts. const fs::path dryDir = scratchRoot / std::format("{}-run-1-dry", h8); Write(dryDir / "prompts" / "stale.txt", "a prompt of another range"); const Process::Captured again = run({"--dry-run"}); std::int64_t prompts = 0; for (const fs::directory_entry& entry : fs::directory_iterator(dryDir / "prompts")) prompts += entry.path().extension() == ".txt" ? 1 : 0; Check("dry run: a second dry run on a hash counts its own prompts, the one before's gone", again.returnCode == 2 && !fs::exists(dryDir / "prompts" / "stale.txt") && prompts == all, std::to_string(prompts)); // Killed in its last gate: one security pass never answers, and the // driver is SIGKILLed once every other pass has. const fs::path scratchA = scratchRoot / std::format("{}-run-1", h8); const Process::Child a = start({}, {{"FAKE_SLEEP", "571"}, {"FAKE_SLEEP_ON", std::format("repo-security:{}", securityUnit)}}, "a"); auto answered = [&] { std::int64_t n = 0; std::error_code error; for (fs::directory_iterator it(scratchA / "out", error), end; !error && it != end; it.increment(error)) { if (it->path().extension() == ".jsonl" && Read(it->path()).contains(R"("type":"result")")) ++n; } return n; }; const bool settled = WaitFor([&] { return calls() == all && answered() == all - 1; }, std::chrono::seconds(30)); Process::Signal(a.pid, SIGKILL); const std::optional aCode = Process::Wait(a.pid, std::chrono::seconds(10)); const std::vector aLeft = LeftoversAfter(fakeDir); Json resumeA; try { resumeA = Dyn::LoadJsonFile(gateDir / std::format("resume-{}.json", h8)); } catch (const std::exception&) { } const Json dumpA = LatestDump(gateDir, h8); Check("SIGKILL: a driver killed in its last gate takes its passes with it (each pass's supervisor ends its tree); its first gate is on record in the checkpoint and the partial dump", settled && aCode == -9 && aLeft.empty() && dumpA.is_object() && Text(dumpA.at("overall")).starts_with("IN PROGRESS - after Gate 1 — Approach") && resumeA.is_object() && resumeA.at("results").size() == 1 && resumeA.at("results").at(0).at("lens") == "repo-better-solution", std::format("settled {}, exit {}, left {}, {} of {} answered, {}", settled, aCode.value_or(0), Dyn::Join(aLeft, "; "), answered(), all, resumeA.dump().substr(0, 300))); // The next run: the first gate resumed, the outputs of the killed run // salvaged, the one pass that never answered bought again. std::int64_t before = calls(); const Process::Captured b = run({}); const Json dumpB = LatestDump(gateDir, h8); const Json& processesB = dumpB.is_object() ? dumpB.at("processes") : Json::array(); Check("the next run resumes the first gate from the checkpoint, salvages every output the killed run finished and buys only the pass that never answered - SEND-READY, exit 0", b.returnCode == 0 && calls() - before == 1 && dumpB.is_object() && dumpB.at("overall") == "SEND-READY" && dumpB.at("resumedLenses") == Json::array({"repo-better-solution"}) && std::ranges::count_if(processesB, [](const Json& r) { return Dyn::Truthy(Dyn::Get(r, "salvaged")); }) == all - gate1 - 1 && Count(processesB, "status", "ok") == all - gate1, std::format("exit {}, {} call(s), {}", b.returnCode, calls() - before, dumpB.is_object() ? Text(dumpB.at("overall")) : b.out + b.err)); const Json stored = State::LoadState(gateDir / "unit-state.json").at("entries"); Check("...and stores the clean block results of the range for reuse", static_cast(stored.size()) >= all - gate1, std::to_string(stored.size())); // Nothing left to buy. before = calls(); const Process::Captured c = run({}); const Json dumpC = LatestDump(gateDir, h8); Check("a run on the same hash has nothing left to buy: every lens resumes, claude is never called, SEND-READY", c.returnCode == 0 && calls() == before && dumpC.is_object() && dumpC.at("overall") == "SEND-READY" && dumpC.at("resumedLenses").size() == 3, std::format("exit {}, {} call(s)", c.returnCode, calls() - before)); // A defect, on a run that reuses nothing; the scratch dir it claims // holds a stop file from before, which is not this run's. const std::string nextRun = std::format("{}-run-{}", h8, RunNumber(dumpC) + 1); const fs::path stale = scratchRoot / nextRun / "stop"; Write(stale, ""); before = calls(); const Process::Captured d = run({"--no-reuse", "--no-salvage"}, {{"FAKE_FIX", std::format("repo-bug-prover:{}", proverUnit)}, {"FAKE_SLEEP", "3"}, {"FAKE_SLEEP_ON", "repo-better-solution:whole:order"}}); const Json dumpD = LatestDump(gateDir, h8); Check("FIX FIRST: a defect in the last gate stops the run there, exit 1 - every pass bought again with --no-reuse --no-salvage", d.returnCode == 1 && calls() - before == all && dumpD.is_object() && dumpD.at("overall") == "FIX FIRST" && dumpD.at("stoppedAt") == "Gate 3 — Proof", std::format("exit {}, {} call(s), {}", d.returnCode, calls() - before, dumpD.is_object() ? Text(dumpD.at("stoppedAt")) : d.out + d.err)); Check("stop: a stop file left in the scratch dir a run claims is dropped - the run ran its course past the watcher's first look", !fs::exists(stale) && dumpD.is_object() && Dyn::Str(dumpD.at("scratch")).ends_with(nextRun), nextRun); // Stopped by `crafter-review stop`'s file, and by SIGTERM, while the // first gate's whole pass is under way: the pass is killed, no later // gate starts, nothing is left running. std::vector widths; for (const bool bySignal : {false, true}) { before = calls(); const std::string name = bySignal ? "term" : "file"; const Process::Child child = start({"--no-reuse", "--no-salvage"}, {{"FAKE_SLEEP", "573"}}, name); const bool running = WaitFor([&] { return calls() > before; }, std::chrono::seconds(30)); // While it runs, the board shows its width: per account, there // being no machine-wide cap. std::string width; try { const Json drivers = Dyn::LoadJsonFile(base / "board.json").at("drivers"); for (auto it = drivers.begin(); it != drivers.end(); ++it) width = Text(Dyn::Get(Dyn::Get(it.value(), "settings"), "concurrency")); } catch (const std::exception&) { } widths.push_back(width); std::string scratch; for (const std::string& line : Lines(base / std::format("{}.out", name))) { if (const std::size_t at = line.rfind("; scratch "); at != std::string::npos) scratch = line.substr(at + 10); } if (bySignal) { Process::Signal(child.pid, SIGTERM); } else if (!scratch.empty()) { Write(fs::path(scratch) / "stop", ""); } const std::optional code = Process::Wait(child.pid, std::chrono::seconds(30)); if (!code) Process::Signal(child.pid, SIGKILL); const std::vector left = LeftoversAfter(fakeDir); const Json dump = LatestDump(gateDir, h8); const std::string why = bySignal ? "stopped by signal 15 (1 running processes killed)" : "stopped by crafter-review stop (1 running processes killed)"; Check(bySignal ? "SIGTERM: the run stops as the stop file stops it - the same abort, the same exit, nothing left running" : "stop file: `crafter-review stop` stops the run - its running pass killed, no later gate started, INCOMPLETE, exit 2, nothing left running", running && code == 2 && calls() - before == 1 && left.empty() && dump.is_object() && dump.at("stoppedAt") == std::format("Gate 1 — Approach - ABORTED ({})", why) && Text(dump.at("overall")).starts_with("INCOMPLETE - NOT SEND-READY"), std::format("running {}, exit {}, {} call(s), left {}, {}", running, code.value_or(-1), calls() - before, Dyn::Join(left, "; "), dump.is_object() ? Text(dump.at("stoppedAt")) : Read(base / std::format("{}.err", name)))); } // No machine-wide width by default (2026-10-31): the dump's args record // none, a live run's board entry shows the per-account one, and a // width below 1 is refused. const Process::Captured noWidth = run({"--concurrency", "0"}); Check("no global cap by default: the dump records none, a live run shows `128 per account` on the board; --concurrency below 1 is a usage error", dumpB.is_object() && dumpB.at("args").at("concurrency").is_null() && widths == std::vector{"128 per account", "128 per account"} && noWidth.returnCode == 3 && noWidth.err.contains("argument --concurrency: a number from 1 up"), std::format("{} | {}", Dyn::Join(widths, ", "), noWidth.err)); // An env file that is not UTF-8: the preflight stops the run. Write(base / "bad.env", std::string("\xff\n", 2)); const Process::Captured badEnv = run({"--no-reuse", "--no-salvage", "--env-file", (base / "bad.env").string()}); Check("preflight: an env file that is not UTF-8 stops the run, exit 3, with the decoder's own words", badEnv.returnCode == 3 && badEnv.err.contains("preflight: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"), badEnv.err); // One seats file for the settings and the tokens: the one --env-file // names as the run's own parser reads it - a unique prefix, the last // of a repeat - with the machine's own file as the decoy the usual // lookup would find; the log names it with the settings put in. const fs::path decoyXdg = base / "decoy-xdg"; Write(decoyXdg / "crafter-review" / "env", "REVIEW_GATES_API_BUDGET_USD=5\nCLAUDE_CODE_OAUTH_TOKEN1=tok-decoy\nANTHROPIC_API_KEY1=sk-ant-decoy\n"); Write(base / "other.env", "REVIEW_GATES_API_BUDGET_USD=7\nREVIEW_GATES_CLAUDE_BIN=/nowhere/claude\nCLAUDE_CODE_OAUTH_TOKEN2=tok-other\nANTHROPIC_API_KEY2=sk-ant-other\n"); const std::map withDecoy = {{"XDG_CONFIG_HOME", decoyXdg.string()}, {"HOST_XDG_CONFIG_HOME", decoyXdg.string()}}; const std::string other = (base / "other.env").string(); const Process::Captured prefixed = run({"--env", other, "--seats", "auto", "--dry-run", "--scratch", (base / "env-prefix").string()}, withDecoy); const Process::Captured repeated = run({"--env-file", (decoyXdg / "crafter-review" / "env").string(), "--env-file", other, "--seats", "auto", "--dry-run", "--scratch", (base / "env-repeat").string()}, withDecoy); auto oneFile = [&](const Process::Captured& dry) { return dry.returnCode == 2 && dry.out.contains(std::format("settings: REVIEW_GATES_API_BUDGET_USD from {} (under", other)) && dry.out.contains("api2 (api)") && dry.out.contains("2 (oauth)") && dry.out.contains(std::format("seats file: REVIEW_GATES_CLAUDE_BIN in {} - no placement setting, read from the process environment alone", other)) && dry.out.contains(std::format("from {}; api-first, budget $7 per api seat per cycle", other)) && !dry.out.contains("api1") && !dry.out.contains("1 (oauth)"); }; Check("--env (a prefix) and the last of a repeated --env-file name the one file the settings are applied from and the tokens are loaded from, never the decoy", oneFile(prefixed) && oneFile(repeated), std::format("{} {}\n{} {}", prefixed.returnCode, prefixed.out + prefixed.err, repeated.returnCode, repeated.out + repeated.err)); // A seats file that can be read only once - a FIFO, as `--env-file // <(gpg -d seats.env.gpg)` gives one - is read once: the run's // settings and its seats both come from that read (round 10). const fs::path fifo = base / "seats.fifo"; std::vector viaFifo = { "sh", "-c", std::format("mkfifo '{0}' && (printf 'REVIEW_GATES_API_BUDGET_USD=9\\nCLAUDE_CODE_OAUTH_TOKEN5=tok-five\\nANTHROPIC_API_KEY5=sk-ant-five\\n' > '{0}' &) && exec \"$@\"", fifo.string()), "sh"}; std::ranges::move(runArgs({"--env-file", fifo.string(), "--seats", "auto", "--dry-run", "--scratch", (base / "env-fifo").string()}), std::back_inserter(viaFifo)); const Process::Captured fromFifo = Process::Run(viaFifo, repo, std::chrono::seconds(60), {}, Env); Check("a seats file that can be read only once (a FIFO) is read once: the run's settings and its seats both come from it", fromFifo.returnCode == 2 && fromFifo.out.contains(std::format("settings: REVIEW_GATES_API_BUDGET_USD from {} (under", fifo.string())) && fromFifo.out.contains("api5 (api)") && fromFifo.out.contains("5 (oauth)") && fromFifo.out.contains(std::format("from {}; api-first, budget $9 per api seat per cycle", fifo.string())), std::format("{} {}{}", fromFifo.returnCode, fromFifo.out, fromFifo.err)); // A filter that names nothing is refused: skipping its empty items // must not leave no filter, which is a full run. const Process::Captured noUnits = run({"--units", ","}); const Process::Captured noLenses = run({"--only", " , "}); Check("--units and --only that name nothing are usage errors (exit 3) - never a full run", noUnits.returnCode == 3 && noUnits.err.contains("argument --units: names no unit id (',')") && noLenses.returnCode == 3 && noLenses.err.contains("argument --only: names no lens file (' , ')"), noUnits.err + noLenses.err); // A patch that is not UTF-8 is no artifact: a preflight stop. const fs::path badArtifact = repo / ".review-gates" / "push" / "bad"; fs::copy(artifact, badArtifact, fs::copy_options::recursive); for (const fs::directory_entry& entry : fs::directory_iterator(badArtifact)) { if (entry.path().extension() == ".patch") std::ofstream(entry.path(), std::ios::binary | std::ios::app) << std::string("\xff\n", 2); } const Process::Captured badPatch = Process::Run({Platform::SelfExe().string(), "run", "--artifact", ".review-gates/push/bad", "--base", baseSha, "--trees", ".", "--dispositions", ".review-gates/push/bad/gate-state/dispositions.json", "--claude-bin", fake.string(), "--scratch", scratchRoot.string(), "--dry-run"}, repo, std::chrono::seconds(60), {}, Env); Check("preflight: a patch that is not UTF-8 stops the run, exit 3, saying so", badPatch.returnCode == 3 && badPatch.err.contains("the artifact's patches are not UTF-8 ('utf-8' codec can't decode byte 0xff"), badPatch.err); // An error that escapes a run ends it with its message, exit 1, // through the run's cleanup. const Process::Captured badReserve = run({"--no-reuse", "--no-salvage"}, {{"REVIEW_GATES_MEM_RESERVE_MB", "x"}}); Check("an error escaping a full run (a reserve that is no number) ends it with its ValueError, exit 1", badReserve.returnCode == 1 && badReserve.err.contains("crafter-review: ValueError: could not convert string to float: 'x'"), badReserve.err); // The commit-truth lens (2026-10-31), alone: a config that turns on // only it (through --config: the tracked config file must stay as // committed, or the tree is dirty) runs Gate 4 - its three text // blocks and its two slot passes (the stand-in answers a SLOT PASS // too) - push-ready, with the code units, the per-commit slots and // the functional lens's slot listed as not reviewed. { Write(base / "facts.json", R"({"modes": {"commits": {"lenses": ["repo-facts"]}}})"); const std::int64_t before = calls(); std::vector truthArgs = runArgs({"--no-reuse", "--no-salvage"}); truthArgs.insert(truthArgs.begin() + 1, {"--config", (base / "facts.json").string()}); const Process::Captured truth = Process::Run(truthArgs, repo, std::chrono::seconds(60), {}, Env); const Json dumpT = LatestDump(gateDir, h8); const Json& processesT = dumpT.is_object() ? dumpT.at("processes") : Json::array(); std::set labels; for (const Json& p : processesT) labels.insert(Text(p.at("label"))); const Json notReviewedT = dumpT.is_object() ? dumpT.at("coverage").at("notReviewed") : Json::array(); auto notReviewed = [&](std::string_view id) { return std::ranges::any_of(notReviewedT, [&](const Json& x) { return x == id; }); }; Check("the commit-truth lens alone: Gate 4 runs its three text blocks and its two slot passes (5 calls) - SEND-READY, exit 0; the code units, the per-commit slots and whole:behaviour are not reviewed", truth.returnCode == 0 && calls() - before == 5 && dumpT.is_object() && dumpT.at("overall") == "SEND-READY" && dumpT.at("stoppedAt").is_null() && labels == std::set{"repo-facts:block:p1:subject", "repo-facts:block:p1:msg", "repo-facts:block:p1:diffstat", "repo-facts:slot:whole:order", "repo-facts:slot:whole:absent"} && Count(processesT, "kind", "slot") == 2 && dumpT.at("lensesOff") == Json::array({"repo-better-solution", "repo-functional", "repo-bug-prover", "repo-security"}) && notReviewed("p1:hello.py#1") && notReviewed("p1:absent") && notReviewed("whole:behaviour") && !notReviewed("whole:order") && !notReviewed("p1:msg"), std::format("exit {}, {} call(s), {}", truth.returnCode, calls() - before, dumpT.is_object() ? Text(dumpT.at("overall")) : truth.out + truth.err)); } // A workbench (2026-10-31): a directory that reviews another // repository - its own config and env file, the artifact wherever // --out says, the lenses' tree the frozen worktree. The run reads the // repository under review from the freeze record: the prompt names // it, not the working directory, and --trees defaults to the // recorded worktree. { const fs::path bench = base / "bench"; fs::create_directories(bench / ".review-gates"); Write(bench / ".review-gates" / "config.json", R"({"modes": {"commits": {"lenses": ["repo-bug-prover"]}}})"); const fs::path out = bench / "journal" / "push" / "y"; const Process::Captured benchFreeze = Process::Run({Platform::SelfExe().string(), "freeze", "--repo", repo.string(), "--out", out.string(), "--worktree", "--base", baseSha}, bench, std::chrono::seconds(60), {}, Env); Json benchRecord; try { benchRecord = Dyn::LoadJsonFile(out / "freeze.json"); } catch (const std::exception&) { } const std::string benchHash = benchRecord.is_object() ? Text(benchRecord.at("hash")) : std::string(); const std::string benchTree = benchRecord.is_object() ? Text(Dyn::Get(benchRecord, "worktree")) : std::string(); Check("a workbench freezes another repository: the record names it, and the worktree is under the workbench's .review-gates/trees/", benchFreeze.returnCode == 0 && benchRecord.is_object() && Text(benchRecord.at("repo")) == Dyn::AbsPath(repo.string()) && benchTree == std::format(".review-gates/trees/repo-{}", benchHash.substr(0, 8)) && fs::is_directory(bench / benchTree), benchFreeze.out + benchFreeze.err); const fs::path benchScratch = base / "bench-scratch"; const Process::Captured benchRun = Process::Run({Platform::SelfExe().string(), "run", "--artifact", "journal/push/y", "--hash", benchHash, "--base", baseSha, "--dispositions", "journal/push/y/gate-state/dispositions.json", "--date", Text(freezeJson.at("date")), "--claude-bin", fake.string(), "--scratch", benchScratch.string(), "--seats", "local", "--no-probe", "--dry-run"}, bench, std::chrono::seconds(60), {}, Env); const fs::path benchDry = benchScratch / std::format("{}-run-1-dry", benchHash.substr(0, 8)); // The run layer is in the system prompts (system-.md). std::string systems; std::error_code benchError; for (fs::directory_iterator it(benchDry, benchError), end; !benchError && it != end; it.increment(benchError)) { const std::string name = it->path().filename().string(); if (name.starts_with("system-") && name.ends_with(".md")) systems += Platform::ReadFile(it->path()).value_or(""); } const std::string benchLog = Platform::ReadFile(benchDry / "log.txt").value_or(""); Check("...and a dry run from the workbench without --trees (exit 2, as every dry run) reads the recorded worktree, names the repository under review in the run layer, not the workbench, and logs it", benchRun.returnCode == 2 && !systems.empty() && systems.contains(std::format("Repository under review (repo), checked out at the range HEAD (the last commit of the artifact): {} -", benchTree)) && !systems.contains("(bench)") && benchLog.contains(std::format("repository under review: {} (read at {})", Dyn::AbsPath(repo.string()), benchTree)), std::format("exit {}, {} bytes of system prompt; {}{}", benchRun.returnCode, systems.size(), benchRun.out, benchRun.err)); } // A stop between two gates: the next gate's pool starts aborted and // runs nothing of it. { const fs::path fx = checkout / "tests" / "Goldens" / "fixture" / "commits"; Run::Run g; g.log = [](const std::string&) {}; g.a.artifact = fx.string(); g.hash = Artifact::ArtifactHash("commits", fx); g.testMode = true; g.cfg = Config::Defaults(); g.environments = Config::Environments(g.cfg); for (const Plan::Lens& lens : Plan::Lenses) { const std::string lf(lens.file); g.charters[lf] = Prompt::Charter(lf); g.charterSha[lf] = Plan::Sha(g.charters[lf]); } const Inventory::Built built = Inventory::Build("commits", fx.string()); g.units = built.out.at("units"); g.lines = built.lines; g.scratch = base / "between"; fs::create_directories(g.scratch); g.calibration = std::make_unique(base / "between" / "memory.json", base, std::map {{"none", 210}}); std::int32_t ran = 0; g.runPass = [&](const Json& job, const std::string&, const fs::path&, std::string_view, std::int64_t, std::int64_t, bool) { ++ran; return std::pair({{"label", job.at("label")}, {"status", "error"}, {"reason", "ran"}}, nullptr); }; g.Stop("crafter-review stop"); const std::vector res = g.RunGate("Gate 3 — Proof", {"repo-bug-prover.md"}); Check("stop between gates: the next gate's pool starts aborted - none of its passes runs, and the abort is the run's", ran == 0 && g.abort && g.abort->starts_with("stopped by crafter-review stop") && res.size() == 1 && !res[0].at("coverageMissing").empty(), g.abort.value_or("None")); } // The dry run's re-slice reads the artifact back from the run layer // by stripping the line-number prefix - `0001| `, five digits from // line 10000 on. A fixed six characters was wrong there: the first // artifact past 9999 lines, the pipeline's own review of 2026-11-01, // refused itself at preflight. { const fs::path big = base / "big"; fs::create_directories(big / ".review-gates"); Write(big / ".review-gates" / "config.json", R"({"modes": {"commits": {"lenses": ["repo-bug-prover"]}}})"); Git(big, {"init", "-q"}); Git(big, {"config", "user.email", "me@example.com"}); Git(big, {"config", "user.name", "Me"}); Write(big / "README.md", "# big\n"); Git(big, {"add", "."}); Git(big, {"commit", "-q", "-m", "base"}); const std::string bigBase = Dyn::Strip(Git(big, {"rev-parse", "HEAD"}).out); std::string wide; for (std::int32_t i = 0; i < 10'050; ++i) wide += std::format("line {}\n", i); Write(big / "wide.txt", wide); Git(big, {"add", "."}); Git(big, {"commit", "-q", "-m", "add a wide file\n\nTen thousand and fifty lines."}); const fs::path bigFake = base / "big-fake"; Write(bigFake, "#!/bin/sh\nexit 1\n"); fs::permissions(bigFake, fs::perms::owner_all); const fs::path bigOut = base / "big-out"; const Process::Captured bigFreeze = Process::Run({Platform::SelfExe().string(), "freeze", "--out", bigOut.string(), "--base", bigBase}, big, std::chrono::seconds(60), {}, Env); Json bigRecord; try { bigRecord = Dyn::LoadJsonFile(bigOut / "freeze.json"); } catch (const std::exception&) { } const std::string bigHash = bigRecord.is_object() ? Text(bigRecord.at("hash")) : std::string(); std::string patchText; std::error_code bigError; for (fs::directory_iterator it(bigOut, bigError), end; !bigError && it != end; it.increment(bigError)) { if (it->path().extension() == ".patch") patchText = Platform::ReadFile(it->path()).value_or(""); } const fs::path bigScratch = base / "big-scratch"; const Process::Captured bigRun = Process::Run({Platform::SelfExe().string(), "run", "--artifact", bigOut.string(), "--hash", bigHash, "--base", bigBase, "--date", "2026-11-01", "--claude-bin", bigFake.string(), "--scratch", bigScratch.string(), "--seats", "local", "--no-probe", "--dry-run"}, big, std::chrono::seconds(120), {}, Env); Json bigDry; try { bigDry = Dyn::LoadJsonFile(bigScratch / std::format("{}-run-1-dry", bigHash.substr(0, 8)) / "dry-run.json"); } catch (const std::exception&) { } Check("an artifact past 9999 lines: the dry run's re-slice of the run layer's artifact matches every unit (the prefix is read to its bar, not six characters)", bigFreeze.returnCode == 0 && std::ranges::count(patchText, '\n') > 10'000 && bigRun.returnCode == 2 && bigDry.is_object() && Dyn::Truthy(bigDry.at("dryRun").at("resliceOk")), std::format("freeze {} run {}: {}{}", bigFreeze.returnCode, bigRun.returnCode, Dyn::Slice(bigRun.out, -600), bigRun.err)); } fs::remove_all(base); return Failures ? 1 : 0; } } int main(int argc, char** argv) { const std::vector args(argv, argv + argc); // With arguments this binary is crafter-review itself - the driver, its // supervisor, its helpers - in native mode whatever builds it. if (args.size() > 1) { Platform::SystemPaths paths = Platform::Paths(); paths.flatpakInfo = "/nonexistent/.flatpak-info"; Platform::SetPaths(paths); return Crafter::Review::Main(args); } try { return Checks(); } catch (const std::exception& error) { std::println(std::cerr, "runs: {}", error.what()); return 1; } }