feat(pool): the pool - stacks, admission, the runner and the scheduler

The allowlists and the instrument checks (mcp_connected, db_answered,
exercised), the stack keys, Stacks and Admission, the Runner that runs one
claude or opencode process per pass, and the Scheduler with its retries,
rate-window pauses, the prompt-prefix warm-up, the seats and the board.
One thread per pass; each worker runs on its own copy of the job.

Every pass runs under the supervisor, so a timeout or an abort takes its
whole tree down; a timed-out pass reports exit -9, a SIGKILLed child's
code. The helpers the lenses run are this binary's `sql` and `totp`.
:Process gains an Append stream for the OpenCode meta line.

The tests cover the pool and its share of the commits-mode, seat-pool and
several-drivers checks, a fake claude under the supervisor included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 08:55:00 +01:00
commit 554a5ddd48
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
8 changed files with 2132 additions and 4 deletions

File diff suppressed because it is too large Load diff

View file

@ -97,9 +97,12 @@ namespace Crafter::Review::Process {
return -1; return -1;
case Stream::Kind::Null: case Stream::Kind::Null:
return open("/dev/null", (reading ? O_RDONLY : O_WRONLY) | O_CLOEXEC); return open("/dev/null", (reading ? O_RDONLY : O_WRONLY) | O_CLOEXEC);
// Created 0666 less the umask, truncated.
case Stream::Kind::File: case Stream::Kind::File:
return reading ? open(stream.file.c_str(), O_RDONLY | O_CLOEXEC) return reading ? open(stream.file.c_str(), O_RDONLY | O_CLOEXEC)
: open(stream.file.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); : open(stream.file.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
case Stream::Kind::Append:
return open(stream.file.c_str(), O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0666);
case Stream::Kind::Pipe: { case Stream::Kind::Pipe: {
std::array<std::int32_t, 2> ends {}; std::array<std::int32_t, 2> ends {};
if (pipe2(ends.data(), O_CLOEXEC) != 0) return -2; if (pipe2(ends.data(), O_CLOEXEC) != 0) return -2;

View file

@ -0,0 +1,269 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Review:Pool;
import std;
import :Json;
import :SeatPool;
import :Memory;
import :PgWire;
// One `claude --print` process per pass, a pool with gating, retries,
// rate-window pauses and progress; with seats every process runs on the seat
// picked for it and a seat's limit pauses that seat, not the pool. What else
// holds a launch back is the pass's environment, never its lens (Admission):
// a pass that drives an application waits for a stack of its own (Stacks),
// and every pass waits until it fits in free memory at its environment's
// measured cost. A pass with the database alone shares the first stack:
// every SQL call is its own transaction, rolled back.
//
// Every pass runs under the supervisor (`crafter-review __supervise`), so a
// timeout, an abort or the driver's death takes its whole tree down - the
// MCP server and Chromium with it.
export namespace Crafter::Review::Pool {
using Log = std::function<void(const std::string&)>;
// (status, reason)
using Verdict = std::pair<std::string, std::string>;
// seats.find_claude_bin
std::string DefaultBin();
// The exact Bash prefix a lens with the database under test runs SQL with:
// this binary's `sql`, spelled absolute (allowlisted as a prefix).
std::string SqlCommand();
// The exact Bash line a lens with an application under test runs for a
// one-time code: this binary's `totp <fixture>`; nullopt without one.
std::optional<std::string> TotpCommand(const std::optional<std::string>& fixture);
// Trees outside the working directory: the CLI must be told (--add-dir)
// and Read allowed there, because --setting-sources '' drops the
// project's additionalDirectories and a read there would prompt.
std::vector<std::string> ExtraDirs(std::span<const std::string> trees);
// A pass with the browser that returned ok, fail closed: the Playwright
// server connected with its tools (2026-10-15: --safe-mode had dropped it
// and four units came back n/a), and the browser answered - an empirical
// lens's whole pass must have called it, and no pass may have had every
// browser call fail.
Verdict McpConnected(const Json& parsed, std::string_view kind = "block", bool empirical = true);
// A pass that ran the SQL helper and never got the database's answer had
// no database: its verdict is not evidence from one.
Verdict DbAnswered(const Json& parsed, std::string_view kind = "block", bool empirical = false);
// Every instrument the environment gives must show it was there.
Verdict Exercised(const Json& parsed, std::string_view environment, std::string_view kind = "block", bool empirical = false);
// A stack's name on the board, the same in every driver: its
// application's address, localhost and 127.0.0.1 one host, a default port
// spelled out; a stack without a URL is its id.
std::string StackKey(const Json& stack);
// The read-only tools, git -C on every tree in both spellings, and what
// the environment adds: the browser tools and curl, the SQL helper, and
// with an application the one-time-code command and the config's extras.
std::vector<std::string> AllowedTools(std::string_view lensFile, std::string_view kind, std::span<const std::string> trees, std::span<const std::string> extra = {},
const std::optional<std::string>& loginFixture = std::nullopt, std::string_view environment = "none");
// The stacks a run's passes use (2026-10-22): the one the config names,
// or the slots brought up side by side. A pass whose environment drives an
// application holds a stack of its own from launch to end, least recently
// returned first; another driver's pass holds one too (`held`, the
// StackKey of every stack the other drivers on this machine hold).
class Stacks {
public:
explicit Stacks(std::vector<Json> stacks);
std::size_t Size() const;
// nullptr when there is none.
const Json* First() const;
const std::vector<Json>& All() const;
const std::string& KeyOf(const std::string& id) const;
// Is a stack free (prefer: the one a resumed session ran on - only
// that one will do)?
bool Can(const std::optional<std::string>& prefer = std::nullopt, const std::set<std::string>& held = {}) const;
Json Take(const std::optional<std::string>& prefer = std::nullopt, const std::set<std::string>& held = {});
void Give(const std::string& id);
std::vector<std::string> Free() const;
private:
std::vector<Json> all;
std::map<std::string, std::string> keys;
std::vector<std::string> free;
mutable std::mutex lock;
};
// May the pool launch this pass now? No per-lens number (2026-10-22): an
// environment that drives an application needs a stack of its own, and the
// pass must fit the memory budget - free memory at the gate's start less
// the reserve, less what the passes in flight were admitted at - with the
// machine still holding its cost free beyond the reserve right now. With
// nothing in flight on the machine a pass is admitted whatever the memory
// says, and the log says so once. `others` is what the other drivers'
// passes hold, as the board's last fold left it.
class Admission {
public:
using CostOf = std::function<double(const Json& job)>;
using NeedsStack = std::function<bool(const Json& job)>;
using Available = std::function<std::optional<double>()>;
Admission(CostOf costOf, NeedsStack needsStack, Stacks* stacks = nullptr, std::optional<double> budget = std::nullopt, double reserve = 0, Available available = {}, Log log = {});
bool Can(const Json& job);
// The pass's memory and its stack taken: _admitted, stack, prevStack
// and stackKey on the job.
void Take(Json& job);
void Give(Json& job);
void SetOthers(SeatPool::Others others);
SeatPool::Others OthersNow() const;
std::int64_t Inflight() const;
double Held() const;
private:
CostOf costOf;
NeedsStack needsStack;
Stacks* stacks;
std::optional<double> budget;
double reserve;
Available available;
Log log;
std::int64_t inflight = 0;
double held = 0;
bool warned = false;
SeatPool::Others others;
mutable std::mutex lock;
std::optional<std::string> Prefer(const Json& job) const;
};
struct RunnerOptions {
std::filesystem::path repo;
std::filesystem::path scratch;
// "": REVIEW_GATES_CLAUDE_BIN, else DefaultBin().
std::string bin;
std::string effort = "xhigh";
Log log;
bool dryRun = false;
std::string harness = "claude";
std::optional<std::string> variant;
std::string opencodeBin;
// The config's functional section: the MCP server, the extra tools.
Json functional = Json::object();
std::optional<std::string> loginFixture;
// {lens file: environment}
std::map<std::string, std::string> environments;
// The first stack's: a pass with the database alone runs there.
std::optional<std::string> dbUrl;
Memory::Sampler* sampler = nullptr;
// The database URLs stay in the driver; a db pass gets the broker's
// address and a token.
PgWire::Broker* broker = nullptr;
};
class Runner {
public:
explicit Runner(RunnerOptions options);
// Every running pass stopped (its supervisor takes the tree down); how
// many.
std::int32_t KillChildren();
std::string EnvOf(const Json& job) const;
// A pass's process environment: the seat's (base), plus - when its
// environment reaches the database - the broker's address and the
// token of the database of the stack it holds (none for a stack
// without one), else the run's first stack's.
std::map<std::string, std::string> ProcessEnv(const Json& job, const std::map<std::string, std::string>& base) const;
// The login fixture of the stack it holds, else the run's.
std::optional<std::string> LoginOf(const Json& job) const;
// The --mcp-config of a pass, nullopt for an environment without the
// browser.
std::optional<std::string> McpConfig(const Json& job) const;
std::vector<std::string> Cmd(const Json& job, const std::filesystem::path& systemPath, std::string_view schema, std::span<const std::string> trees, std::int64_t maxTurns, bool persist,
const std::string& sessionId, bool resume = false) const;
// One pass: (record, the structured output or null).
std::pair<Json, Json> Run(const Json& job, const std::string& prompt, const std::filesystem::path& systemPath, std::string_view schema, std::span<const std::string> trees,
std::int64_t maxTurns, std::int64_t timeoutMin, bool persist = false);
// The seats a job's `seat` names, as they were when the pool began:
// only what never changes (id, kind, secret) is read from them.
void SetSeats(const std::vector<Json>& seats);
const std::string& Bin() const;
// The environment every pass starts from.
std::map<std::string, std::string>& Env();
const std::filesystem::path& Scratch() const;
std::filesystem::path opencodeHome;
private:
RunnerOptions options;
std::map<std::string, std::string> env;
std::map<std::string, Json> seatsById;
std::map<std::int64_t, bool> running;
mutable std::mutex lock;
std::pair<Json, Json> RunOpenCode(const Json& job, const std::string& prompt, const std::filesystem::path& systemPath, std::span<const std::string> trees, std::int64_t timeoutMin,
const Json& seat, Json rec, const std::filesystem::path& out, const std::filesystem::path& err, const std::string& sessionId);
std::optional<std::int32_t> Wait(std::int64_t pid, std::int64_t timeoutMin, bool& timedOut);
};
struct SchedulerOptions {
Runner* runner = nullptr;
std::int64_t concurrency = 8;
Log log;
std::optional<std::filesystem::path> progressPath;
double pauseAt = 85;
double maxWaitMin = 360;
// The key of a pass's prompt prefix (nullopt: none), which the driver
// keeps on the job as `cacheKey`; and whether a running pass's first
// response is in.
std::function<std::optional<std::string>(Json& job)> cacheKey;
std::function<bool(const Json& job)> warmProbe;
double warmTtl = 240;
double warmTimeout = 180;
double warmPoll = 5;
std::vector<Json>* seats = nullptr;
// null: from the environment.
Json seatConfig;
Json apiConfig;
SeatPool::Ledger* ledger = nullptr;
std::function<double()> now;
Admission* admission = nullptr;
SeatPool::Board* board = nullptr;
double noRoomWait = 30;
// A stop that came before this pool (a signal, `crafter-review stop`):
// the pool starts aborted.
std::optional<std::string> abort;
};
struct Done {
Json job;
Json rec;
Json raw;
};
// Runs jobs on a pool; onDone(job, rec, raw) -> the follow-up jobs (the
// blocks after a passing whole pass). Retries: error/timeout once (x1.5
// budget for a timeout); limit -> pause until resetsAt (or a backoff) and
// requeue (<= 4); auth or a seven_day rejection -> abort. With seats every
// launch is placed (with the board: placed, admitted and registered under
// its lock, the pool width the machine's cap), every finished process
// settled onto its seat and taken off the board. One thread per pass.
class Scheduler {
public:
using ExecJob = std::function<std::pair<Json, Json>(const Json& job, std::int64_t attempt)>;
using OnDone = std::function<std::vector<Json>(const Json& job, const Json& rec, const Json& raw)>;
explicit Scheduler(SchedulerOptions options);
const std::vector<Done>& Run(std::vector<Json> jobs, const ExecJob& exec, const OnDone& onDone);
// From any thread: the pool stops launching and returns once what runs
// has ended.
void Abort(const std::string& reason);
std::optional<std::string> Aborted() const;
const std::vector<Done>& DoneList() const;
// pause_until: 0 until a pause raised it.
Json PauseUntil() const;
private:
SchedulerOptions o;
std::vector<Done> done;
std::optional<std::string> abort;
Json pauseUntil = 0;
Json roomWait;
double waitedMin = 0;
bool heldOff = false;
std::map<std::pair<std::string, std::string>, double> warm;
std::map<std::pair<std::string, std::string>, std::pair<std::string, double>> warming;
mutable std::mutex lock;
std::condition_variable wake;
bool Capped(const Json& job);
bool Launchable(Json& job, const std::vector<Json*>& running, double now, const std::optional<std::string>& seatId);
void RaisePause(double until);
void Sleep(double seconds);
void Progress(const std::vector<std::string>& running, const std::vector<std::string>& queued);
void Log(const std::string& line) const;
};
}

View file

@ -32,6 +32,9 @@ export namespace Crafter::Review::Process {
// Opened for reading (stdin) or created and truncated (stdout, // Opened for reading (stdin) or created and truncated (stdout,
// stderr). // stderr).
File, File,
// stdout or stderr after what the file already holds: a line the
// driver wrote before the child starts stays first.
Append,
// A pipe whose other end Start() returns. // A pipe whose other end Start() returns.
Pipe, Pipe,
}; };

View file

@ -29,5 +29,6 @@ export import :Mcp;
export import :Seats; export import :Seats;
export import :SeatPool; export import :SeatPool;
export import :OpenCode; export import :OpenCode;
export import :Pool;
export import :Freeze; export import :Freeze;
export import :Driver; export import :Driver;

View file

@ -13,7 +13,7 @@ using namespace Crafter;
namespace { namespace {
// The umbrella, then the partitions in import order: each after the ones // The umbrella, then the partitions in import order: each after the ones
// it imports, which is also the order the module map in AGENTS.md uses. // it imports, which is also the order the module map in AGENTS.md uses.
constexpr std::array<std::string_view, 29> Interfaces = { constexpr std::array<std::string_view, 30> Interfaces = {
"interfaces/Crafter.Review", "interfaces/Crafter.Review",
"interfaces/Crafter.Review-Unicode", "interfaces/Crafter.Review-Unicode",
"interfaces/Crafter.Review-Json", "interfaces/Crafter.Review-Json",
@ -41,10 +41,11 @@ namespace {
"interfaces/Crafter.Review-Seats", "interfaces/Crafter.Review-Seats",
"interfaces/Crafter.Review-SeatPool", "interfaces/Crafter.Review-SeatPool",
"interfaces/Crafter.Review-OpenCode", "interfaces/Crafter.Review-OpenCode",
"interfaces/Crafter.Review-Pool",
"interfaces/Crafter.Review-Freeze", "interfaces/Crafter.Review-Freeze",
"interfaces/Crafter.Review-Driver", "interfaces/Crafter.Review-Driver",
}; };
constexpr std::array<std::string_view, 26> Implementations = { constexpr std::array<std::string_view, 27> Implementations = {
"implementations/Crafter.Review-Text", "implementations/Crafter.Review-Text",
"implementations/Crafter.Review-Crypto", "implementations/Crafter.Review-Crypto",
"implementations/Crafter.Review-Platform", "implementations/Crafter.Review-Platform",
@ -69,6 +70,7 @@ namespace {
"implementations/Crafter.Review-Seats", "implementations/Crafter.Review-Seats",
"implementations/Crafter.Review-SeatPool", "implementations/Crafter.Review-SeatPool",
"implementations/Crafter.Review-OpenCode", "implementations/Crafter.Review-OpenCode",
"implementations/Crafter.Review-Pool",
"implementations/Crafter.Review-Freeze", "implementations/Crafter.Review-Freeze",
"implementations/Crafter.Review-Driver", "implementations/Crafter.Review-Driver",
}; };
@ -80,7 +82,7 @@ namespace {
// One per directory under tests/, each an ordinary main() that links the // One per directory under tests/, each an ordinary main() that links the
// library: exit 0 passes, nonzero fails, 77 skips. // library: exit 0 passes, nonzero fails, 77 skips.
constexpr std::array<std::string_view, 18> Tests = { constexpr std::array<std::string_view, 20> Tests = {
"Crypto", "Crypto",
"Text", "Text",
"Platform", "Platform",
@ -99,6 +101,8 @@ namespace {
"SeatPool", "SeatPool",
"Drivers", "Drivers",
"OpenCode", "OpenCode",
"Runner",
"Scheduler",
}; };
// The version the binary reports: CI sets it from the tag, a checkout // The version the binary reports: CI sets it from the tag, a checkout

277
tests/Runner/main.cpp Normal file
View file

@ -0,0 +1,277 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// The pool's command lines: the allowlists and the deny list, the MCP
// config, the command line by environment, the process environment of a
// pass with the database, mcp_connected and exercised, and the stream
// accounting behind them. The helpers are this binary's `sql` and `totp`.
#include <stdlib.h>
import std;
import Crafter.Review;
namespace fs = std::filesystem;
using namespace Crafter::Review;
namespace {
std::int32_t Failures = 0;
const std::string Fn(Plan::FunctionalLens);
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;
}
bool Has(const std::vector<std::string>& list, std::string_view item) {
return std::ranges::contains(list, item);
}
std::ptrdiff_t Index(const std::vector<std::string>& list, std::string_view item) {
const auto at = std::ranges::find(list, item);
return at == list.end() ? -1 : at - list.begin();
}
// c[c.index(flag) + 1]
std::string After(const std::vector<std::string>& c, std::string_view flag) {
const std::ptrdiff_t at = Index(c, flag);
return at < 0 || at + 1 >= static_cast<std::ptrdiff_t>(c.size()) ? std::string() : c[static_cast<std::size_t>(at + 1)];
}
Json Job(std::string_view label, std::string_view lensFile) {
return {{"label", label}, {"model", "m"}, {"lensFile", lensFile}, {"kind", "block"}};
}
Json With(Json value, const Json& changes) {
for (auto it = changes.begin(); it != changes.end(); ++it) value[it.key()] = it.value();
return value;
}
}
int main() {
std::println("commits mode (the pool's part): allowlists, the deny list, the MCP config, the command line, the process environment, mcp_connected, exercised");
Platform::SetMode(Platform::Mode::Native);
const std::string cwd = fs::current_path().string();
const std::vector<std::string> here = {"."};
const std::vector<std::string> al = Pool::AllowedTools("repo-bug-prover.md", "block", here);
Check("static lens allowlist: git -C . and absolute, python3 -, no browser tool, no curl, no Read(//) rule for the cwd",
Has(al, "Bash(git -C . show:*)") && Has(al, std::format("Bash(git -C {} show:*)", cwd)) && Has(al, "Bash(python3 -:*)") && std::ranges::none_of(al, [](const std::string& x) { return x.starts_with("mcp__"); })
&& !Has(al, "Bash(curl:*)") && std::ranges::none_of(al, [](const std::string& x) { return x.starts_with("Read("); }));
const std::vector<std::string> extras = {"Bash(psql:*)", "mcp__x__y"};
const std::string fixture = "cypress/fixtures/created-user.json";
const std::vector<std::string> af = Pool::AllowedTools(Fn, "block", here, extras, fixture, "browser");
const std::string totp = std::format("Bash({}:*)", Pool::TotpCommand(fixture).value_or(""));
Check("functional allowlist: every Playwright tool by name, curl, the TOTP command as a prefix, the extra tools",
std::ranges::all_of(Tools::McpToolNames(), [&](const std::string& t) { return Has(af, t); }) && Has(af, "Bash(curl:*)") && Has(af, totp) && Has(af, "Bash(psql:*)") && Has(af, "mcp__x__y")
&& Pool::TotpCommand(fixture)->starts_with(std::format("{} totp ", Platform::SelfExe().string())));
const std::vector<std::string> psql = {"Bash(psql:*)"};
const std::vector<std::string> staticBrowser = Pool::AllowedTools("repo-bug-prover.md", "block", here, {}, std::nullopt, "browser");
std::vector<std::string> wanted = Tools::McpToolNames();
wanted.emplace_back("Bash(curl:*)");
const std::vector<std::string> functionalNone = Pool::AllowedTools(Fn, "block", here, psql, std::string("u.json"), "none");
Check("the environment carries the instrument, not the lens name: a static lens with browser gets the browser tools and curl; the functional lens with none gets neither, nor the extras or the TOTP command",
std::ranges::all_of(wanted, [&](const std::string& t) { return Has(staticBrowser, t); })
&& std::ranges::none_of(functionalNone, [](const std::string& x) { return x.starts_with("mcp__") || x == "Bash(curl:*)" || x == "Bash(psql:*)" || x.contains(" totp "); }));
Check("the browser allowlist keeps the order of the functional allowlist it replaces: the browser tools, curl, the TOTP command, the extras",
Index(af, "mcp__playwright__browser_navigate") < Index(af, "Bash(curl:*)") && Index(af, "Bash(curl:*)") < Index(af, totp) && Index(af, totp) < Index(af, "Bash(psql:*)")
&& af == Pool::AllowedTools(Fn, "block", here, extras, fixture, "browser"));
const std::vector<std::string>& deny = Tools::Deny();
Check("deny list: run_code_unsafe, package managers, stacks, writes, a database client that commits",
Has(deny, "mcp__playwright__browser_run_code_unsafe") && Has(deny, "Bash(npm:*)") && Has(deny, "Bash(supabase:*)") && Has(deny, "Write") && Has(deny, "Bash(psql:*)"));
const std::string sq = std::format("Bash({}:*)", Pool::SqlCommand());
const std::vector<std::string> redis = {"Bash(redis-cli:*)"};
const std::vector<std::string> asb = Pool::AllowedTools("repo-bug-prover.md", "block", here, redis, std::string("u.json"), "supabase");
Check("supabase allowlist: the SQL helper as its exact prefix (absolute, this binary's sql); no browser tool, no curl, no TOTP command, no extras",
Has(asb, sq) && Pool::SqlCommand() == std::format("{} sql", Platform::SelfExe().string())
&& std::ranges::none_of(asb, [](const std::string& x) { return x.starts_with("mcp__") || x == "Bash(curl:*)" || x == "Bash(redis-cli:*)" || x.contains(" totp "); }));
const std::vector<std::string> abs = Pool::AllowedTools(Fn, "block", here, redis, std::string("u.json"), "browser+supabase");
const std::string totpU = std::format("Bash({}:*)", Pool::TotpCommand("u.json").value_or(""));
Check("browser+supabase allowlist: the browser tools, curl, the SQL helper, the TOTP command, the extras - in that order; browser alone has no SQL helper",
Index(abs, "mcp__playwright__browser_navigate") < Index(abs, "Bash(curl:*)") && Index(abs, "Bash(curl:*)") < Index(abs, sq) && Index(abs, sq) < Index(abs, totpU) && Index(abs, totpU) < Index(abs, "Bash(redis-cli:*)")
&& !Has(Pool::AllowedTools(Fn, "block", here, {}, std::string("u.json"), "browser"), sq) && !Has(Pool::AllowedTools(Fn, "block", here, {}, std::string("u.json"), "none"), sq));
const std::vector<std::string> outside = {"/home/x/repo"};
const std::vector<std::string> al2 = Pool::AllowedTools("repo-bug-prover.md", "block", outside);
Check("allowlist (outside tree): Read(//abs/**)", Has(al2, "Read(//home/x/repo/**)") && Has(al2, "Bash(git -C /home/x/repo show:*)"));
const std::vector<std::string> trees = {".", "sub", "/home/x/repo", "/home/x/repo"};
Check("extra_dirs: the cwd and paths inside it dropped, outside kept once", Pool::ExtraDirs(trees) == std::vector<std::string>{"/home/x/repo"});
// The MCP config and the command line.
const Json npx = {{"mcp", {{"command", "npx"}, {"args", nullptr}}}};
const Json mc = Mcp::PlaywrightConfig(npx, "/tmp/out");
Check("mcp config: the pinned server, headless, isolated, the output dir appended",
mc.at("mcpServers").at("playwright").at("args") == Json::array({"-y", std::format("@playwright/mcp@{}", Mcp::PlaywrightMcpVersion), "--headless", "--isolated", "--browser", "chromium", "--output-dir", "/tmp/out"})
&& mc.at("mcpServers").at("playwright").at("command") == "npx");
const Json own = {{"mcp", {{"command", "playwright-mcp"}, {"args", {"--headless", "--output-dir", "/x"}}}}};
Check("a consumer's own command and args are kept; an output dir it names is not overridden",
Dyn::Equal(Mcp::PlaywrightConfig(own, "/tmp/out").at("mcpServers").at("playwright"), {{"command", "playwright-mcp"}, {"args", {"--headless", "--output-dir", "/x"}}}));
const fs::path td = fs::temp_directory_path() / std::format("crafter-review-runner-{}", Platform::PublishedPid());
fs::remove_all(td);
fs::create_directories(td);
const Json functional = {{"mcp", {{"command", "npx"}, {"args", nullptr}}}, {"extraTools", Json::array()}};
auto options = [&](std::map<std::string, std::string> environments) {
return Pool::RunnerOptions {.repo = cwd, .scratch = td, .bin = "/bin/true", .effort = "xhigh", .log = [](const std::string&) {}, .dryRun = true, .functional = functional,
.environments = std::move(environments)};
};
{
const Pool::Runner runner(options({{Fn, "browser"}}));
const std::vector<std::string> cf = runner.Cmd(Job("f", Fn), "/dev/null", "{}", here, 20, false, "sid");
const std::vector<std::string> cs = runner.Cmd(Job("s", "repo-bug-prover.md"), "/dev/null", "{}", here, 20, false, "sid");
Check("cmd: the functional pass gets --mcp-config with its own output dir and the browser tools allowed; a static pass gets neither; both keep --tools Read,Glob,Grep,Bash and --strict-mcp-config",
Has(cf, "--mcp-config") && After(cf, "--mcp-config").contains((td / "playwright" / "f").string()) && After(cf, "--allowedTools").contains("mcp__playwright__browser_snapshot")
&& !Has(cs, "--mcp-config") && !After(cs, "--allowedTools").contains("mcp__playwright") && After(cf, "--tools") == "Read,Glob,Grep,Bash" && Has(cs, "--strict-mcp-config")
&& After(cf, "--disallowedTools").contains("mcp__playwright__browser_run_code_unsafe"));
Check("cmd: --safe-mode drops MCP servers, so the functional pass runs without it and the static pass keeps it", !Has(cf, "--safe-mode") && Has(cs, "--safe-mode"));
}
{
const Pool::Runner rb(options({{"repo-bug-prover.md", "browser"}}));
const std::vector<std::string> cb = rb.Cmd(Job("b", "repo-bug-prover.md"), "/dev/null", "{}", here, 20, false, "sid");
const std::vector<std::string> cfn = rb.Cmd(Job("f", Fn), "/dev/null", "{}", here, 20, false, "sid");
Check("cmd by environment: a static lens given the browser gets --mcp-config, the browser tools and no --safe-mode; a lens with no environment entry runs as none",
Has(cb, "--mcp-config") && !Has(cb, "--safe-mode") && After(cb, "--allowedTools").contains("mcp__playwright__browser_snapshot") && !Has(cfn, "--mcp-config") && Has(cfn, "--safe-mode")
&& !After(cfn, "--allowedTools").contains("mcp__playwright"));
}
{
const std::string url = "postgresql://postgres:hunter2@127.0.0.1:54322/postgres";
// An operator's shell values never reach a pass.
setenv(std::string(PgWire::UrlEnv).c_str(), "postgresql://leaked@127.0.0.1:1/x", 1);
setenv(std::string(PgWire::BrokerEnv).c_str(), "127.0.0.1:1#leaked", 1);
PgWire::Broker broker;
broker.Start();
Pool::RunnerOptions withDb = options({{"repo-bug-prover.md", "supabase"}, {Fn, "browser+supabase"}});
withDb.dbUrl = url;
withDb.broker = &broker;
Pool::Runner rd(withDb);
unsetenv(std::string(PgWire::UrlEnv).c_str());
unsetenv(std::string(PgWire::BrokerEnv).c_str());
const std::vector<std::string> cd = rd.Cmd(Job("d", "repo-bug-prover.md"), "/dev/null", "{}", here, 20, false, "sid");
const std::vector<std::string> cbs = rd.Cmd(Job("e", Fn), "/dev/null", "{}", here, 20, false, "sid");
Check("cmd: the supabase environment keeps --safe-mode (no MCP server) and gets the SQL helper allowed; browser+supabase gets --mcp-config, the helper and no --safe-mode; the URL is on no command line",
Has(cd, "--safe-mode") && !Has(cd, "--mcp-config") && After(cd, "--allowedTools").contains(sq) && Has(cbs, "--mcp-config") && !Has(cbs, "--safe-mode") && After(cbs, "--allowedTools").contains(sq)
&& !(Dyn::Join(cd, " ") + Dyn::Join(cbs, " ")).contains("hunter2"));
const Json jd = {{"lensFile", "repo-bug-prover.md"}};
const Json js = {{"lensFile", "repo-better-solution.md"}};
const Json jf = {{"lensFile", Fn}};
const std::string brokerEnv(PgWire::BrokerEnv);
const std::map<std::string, std::string> ed = rd.ProcessEnv(jd, rd.Env());
const std::map<std::string, std::string> ef = rd.ProcessEnv(jf, rd.Env());
const std::map<std::string, std::string> es = rd.ProcessEnv(js, rd.Env());
auto token = [&](const std::map<std::string, std::string>& e) {
const auto found = e.find(brokerEnv);
const std::string value = found == e.end() ? std::string("#") : found->second;
return broker.UrlOf(value.substr(value.rfind('#') + 1));
};
auto shaped = [](std::string_view address) {
// ^127\.0\.0\.1:\d+#[0-9a-f]{32}$
if (!address.starts_with("127.0.0.1:")) return false;
const std::size_t hash = address.find('#');
if (hash == std::string_view::npos || hash == 10) return false;
const std::string_view port = address.substr(10, hash - 10);
const std::string_view tok = address.substr(hash + 1);
return std::ranges::all_of(port, [](char c) { return c >= '0' && c <= '9'; }) && tok.size() == 32 && std::ranges::all_of(tok, [](char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); });
};
bool leaked = false;
for (const auto* e : {&ed, &ef}) {
for (const auto& [k, v] : *e) leaked = leaked || v.contains("hunter2");
}
Check("process_env: a db pass gets the broker's address and its database's token - never the URL or its password; a static pass gets neither; the operator's shell values are dropped",
shaped(ed.at(brokerEnv)) && token(ed) == url && token(ef) == url && !leaked && !ed.contains(std::string(PgWire::UrlEnv)) && !es.contains(brokerEnv) && !rd.Env().contains(std::string(PgWire::UrlEnv))
&& !rd.Env().contains(brokerEnv));
const std::string ownDb = "postgresql://postgres:postgres@127.0.0.1:20222/postgres";
Pool::RunnerOptions noBroker = options({{"repo-bug-prover.md", "supabase"}});
noBroker.dbUrl = url;
Check("process_env: a pass that holds a stack gets that stack's database and no other - a stack without one gets none, never the first stack's (finding 1)",
token(rd.ProcessEnv({{"lensFile", Fn}, {"stack", {{"id", "slot 2"}, {"dbUrl", ownDb}}}}, rd.Env())) == ownDb
&& rd.ProcessEnv({{"lensFile", Fn}, {"stack", {{"id", "slot 2"}, {"dbUrl", nullptr}}}}, rd.Env()).at(brokerEnv).empty()
&& Pool::Runner(noBroker).ProcessEnv(jd, {}).at(brokerEnv).empty());
broker.Close();
}
auto init = [](const Json& servers, const Json& tools, const Json& browser = Json()) {
return Json {{"init", {{"mcp_servers", servers}, {"tools", tools}}}, {"browser", browser.is_null() ? Json {{"calls", 2}, {"errors", 0}} : browser}};
};
const Json conn = Json::array({{{"name", "playwright"}, {"status", "connected"}}});
const Json none = Json::array();
const Json read = Json::array({"Read"});
const Json pw = Json::array({"mcp__playwright__x"});
const Json zero = {{"calls", 0}, {"errors", 0}};
const Pool::Verdict ok = {"ok", ""};
Check("mcp_connected: ok only with a connected playwright server AND its tools in the init line; else an error that says so",
Pool::McpConnected(init(conn, Json::array({"Read", "mcp__playwright__browser_snapshot"}))) == ok && Pool::McpConnected(init(none, read)).first == "error"
&& Pool::McpConnected(init(none, read)).second.contains("did not connect") && Pool::McpConnected(init(Json::array({{{"name", "playwright"}, {"status", "failed"}}}), none)).first == "error"
&& Pool::McpConnected({{"init", nullptr}}).first == "error");
Check("mcp_connected: a whole pass with no browser call, or any pass whose every browser call failed, is an error; a block pass with no browser call is allowed; one answered call is enough",
Pool::McpConnected(init(conn, pw, zero), "whole").second.contains("no browser call") && Pool::McpConnected(init(conn, pw, zero), "block") == ok
&& Pool::McpConnected(init(conn, pw, {{"calls", 3}, {"errors", 3}}), "block").second.contains("every browser call")
&& Pool::McpConnected(init(conn, pw, {{"calls", 3}, {"errors", 3}}), "whole").second.contains("every browser call") && Pool::McpConnected(init(conn, pw, {{"calls", 3}, {"errors", 2}}), "whole") == ok);
Check("mcp_connected: a static lens given the browser (not empirical) may finish a whole pass without a browser call; a server that did not connect or answered every call with an error still fails it",
Pool::McpConnected(init(conn, pw, zero), "whole", false) == ok && Pool::McpConnected(init(conn, pw, {{"calls", 2}, {"errors", 2}}), "whole", false).first == "error"
&& Pool::McpConnected(init(none, read), "block", false).first == "error");
Check("exercised: 'browser' is mcp_connected with the lens's empirical flag, 'none' is ok as classified",
Pool::Exercised(init(none, read), "browser", "whole", true) == Pool::McpConnected(init(none, read), "whole", true) && Pool::Exercised(init(conn, pw, zero), "browser", "whole", true).first == "error"
&& Pool::Exercised(init(none, read), "none", "whole", true) == ok);
std::vector<std::string> names;
for (const Plan::Environment& e : Plan::Environments) names.emplace_back(e.name);
const Plan::Environment& browser = *Plan::FindEnvironment("browser");
const Plan::Environment& noneEnv = *Plan::FindEnvironment("none");
const Plan::Environment& supabase = *Plan::FindEnvironment("supabase");
const Plan::Environment& both = *Plan::FindEnvironment("browser+supabase");
auto tools = [](const Plan::Environment& e) { return std::vector<std::string>(e.tools.begin(), e.tools.end()); };
bool otherEmpirical = false;
for (const Plan::Lens& l : Plan::Lenses) otherEmpirical = otherEmpirical || (l.file != Fn && l.empirical);
Check("environments: exactly none, browser, supabase, browser+supabase - a new entry first meets the seven-item list in plan.ENVIRONMENTS (the environment in the reuse keys, _resumable and _salvage; its prompt texts; pool.exercised; validate/preflight; DENY; the opencode refusal; a firing proof)",
names == std::vector<std::string>{"none", "browser", "supabase", "browser+supabase"} && browser.mcp == "playwright" && tools(browser) == std::vector<std::string>{"Bash(curl:*)"} && browser.app && !browser.db
&& browser.memMb == Plan::MemBrowser && noneEnv.mcp.empty() && tools(noneEnv).empty() && !noneEnv.app && !noneEnv.db && noneEnv.memMb == Plan::MemNone && supabase.mcp.empty() && tools(supabase).empty()
&& !supabase.app && supabase.db && supabase.memMb == Plan::MemNone && both.mcp == browser.mcp && tools(both) == tools(browser) && both.app && both.db && both.memMb == browser.memMb
&& Plan::LensOf(Fn).empirical && !otherEmpirical);
// The stream accounting behind it: a navigate the server answered with an
// error, a snapshot it answered.
const std::vector<std::string> ev = {
R"({"type":"system","subtype":"init","mcp_servers":[{"name":"playwright","status":"connected"}],"tools":["mcp__playwright__browser_navigate"]})",
R"({"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"mcp__playwright__browser_navigate","input":{}},{"type":"tool_use","id":"t2","name":"Bash","input":{}}]}})",
R"({"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"t1","is_error":true,"content":"Chromium distribution chrome is not found"},{"type":"tool_result","tool_use_id":"t2","content":"ok"}]}})",
R"({"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"t3","name":"mcp__playwright__browser_snapshot","input":{}}]}})",
R"({"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"t3","content":"- page"}]}})",
R"({"type":"result","subtype":"success","is_error":false,"structured_output":{"verdict":"SEND-READY","findings":[],"notes":[],"coverage":[]},"usage":{"input_tokens":1,"output_tokens":1},"num_turns":2})",
};
const Json ps = Stream::ParseStream(Dyn::Join(ev, "\n"));
Check("stream: browser calls and their errors are counted by tool_use id (2 calls, 1 error), Bash errors are not browser errors",
Dyn::Equal(ps.at("browser"), {{"calls", 2}, {"errors", 1}}) && ps.at("toolErrors") == 1 && Pool::McpConnected(ps, "whole") == ok);
const std::vector<std::string> ev2 = {ev[0], ev[1], ev[2], ev[5]};
const Json ps2 = Stream::ParseStream(Dyn::Join(ev2, "\n"));
Check("...one failed navigate and nothing else -> every browser call failed -> error", Dyn::Equal(ps2.at("browser"), {{"calls", 1}, {"errors", 1}}) && Pool::McpConnected(ps2, "block").first == "error");
// The helper as the Flatpak spells it: what the stream recognises is the
// program's name, whatever path a build runs it from.
const std::string sqlCommand = "/app/bin/crafter-review sql";
auto sqlEv = [&](bool answered, const std::optional<std::string>& cmd = std::nullopt, const Json& content = Json()) {
const Json result = content.is_null() ? Json(answered ? std::format("{} to postgres on 127.0.0.1:54322 as postgres - ONE transaction\n SELECT 1", PgWire::Answered)
: std::format("{} - cannot connect", PgWire::NoDatabase))
: content;
return std::vector<std::string> {
R"({"type":"system","subtype":"init","mcp_servers":[],"tools":["Bash"]})",
DumpSpaced({{"type", "assistant"},
{"message", {{"role", "assistant"}, {"content", Json::array({{{"type", "tool_use"}, {"id", "s1"}, {"name", "Bash"}, {"input", {{"command", cmd.value_or(std::format("{} <<'SQL'\nselect 1;\nSQL", sqlCommand))}}}}})}}}}),
DumpSpaced({{"type", "user"}, {"message", {{"role", "user"}, {"content", Json::array({{{"type", "tool_result"}, {"tool_use_id", "s1"}, {"is_error", !answered}, {"content", result}}})}}}}),
ev[5],
};
};
const Json pa = Stream::ParseStream(Dyn::Join(sqlEv(true), "\n"));
const Json pn = Stream::ParseStream(Dyn::Join(sqlEv(false), "\n"));
Check("stream: a SQL helper call is counted by its command, and answered by the helper's connected line in its result",
Dyn::Equal(pa.at("db"), {{"calls", 1}, {"answered", 1}}) && Dyn::Equal(pn.at("db"), {{"calls", 1}, {"answered", 0}}) && Dyn::Equal(pa.at("browser"), zero));
Check("stream: the helper inside a compound command counts; the marker in a list-of-blocks result counts; the marker mid-line does not; another python3 script is no SQL call",
Dyn::Equal(Stream::ParseStream(Dyn::Join(sqlEv(true, std::format("cd . && {} 'select 1'", sqlCommand)), "\n")).at("db"), {{"calls", 1}, {"answered", 1}})
&& Stream::ParseStream(Dyn::Join(sqlEv(true, std::nullopt, Json::array({{{"type", "text"}, {"text", std::format("{} to x", PgWire::Answered)}}})), "\n")).at("db").at("answered") == 1
&& Stream::ParseStream(Dyn::Join(sqlEv(true, std::nullopt, Json(std::format("echo says {}", PgWire::Answered))), "\n")).at("db").at("answered") == 0
&& Dyn::Equal(Stream::ParseStream(Dyn::Join(sqlEv(true, std::string("python3 - <<EOF\nprint(1)\nEOF")), "\n")).at("db"), {{"calls", 0}, {"answered", 0}}));
const std::vector<std::string> noSql = {sqlEv(true)[0], ev[5]};
Check("exercised: supabase is db_answered - a pass whose every SQL call missed the database is an error, a pass with no SQL call is ok (a lens may judge by the code)",
Pool::Exercised(pa, "supabase", "block") == ok && Pool::Exercised(pn, "supabase", "block").first == "error" && Pool::Exercised(pn, "supabase", "whole", true).second.contains("reached the database")
&& Pool::Exercised(Stream::ParseStream(Dyn::Join(noSql, "\n")), "supabase", "whole", true) == ok && Pool::Exercised(pn, "none", "block") == ok);
const Json bothOk = With(pa, {{"init", {{"mcp_servers", conn}, {"tools", pw}}}, {"browser", {{"calls", 2}, {"errors", 0}}}});
Check("exercised: browser+supabase needs both - the browser (connected, used by the empirical whole pass) and, when SQL ran, the database",
Pool::Exercised(bothOk, "browser+supabase", "whole", true) == ok && Pool::Exercised(With(pa, {{"init", {{"mcp_servers", Json::array()}}}}), "browser+supabase", "block").second.contains("did not connect")
&& Pool::Exercised(With(bothOk, {{"db", {{"calls", 2}, {"answered", 0}}}}), "browser+supabase", "whole", true).second.contains("reached the database")
&& Pool::Exercised(With(bothOk, {{"browser", zero}}), "browser+supabase", "whole", true).second.contains("no browser call"));
fs::remove_all(td);
return Failures == 0 ? 0 : 1;
}

506
tests/Scheduler/main.cpp Normal file
View file

@ -0,0 +1,506 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// The pool at work: re-placement after a seat's failure, the Runner on a
// fake claude (the seat, the MCP check, resume, the database broker), a
// no-room wait other drivers hold, the machine-wide cap, the stack keys, the
// admission against other drivers' memory and stacks, and the race for the
// last stack.
//
// The test binary is the supervisor every pass runs under (`__supervise`);
// another driver is a live `sleep 300`.
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
import std;
import Crafter.Review;
namespace fs = std::filesystem;
using namespace Crafter::Review;
using namespace std::chrono_literals;
namespace {
std::int32_t Failures = 0;
const std::string Fn(Plan::FunctionalLens);
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) {
return Platform::ReadFile(path).value_or("");
}
Json With(Json value, const Json& changes) {
for (auto it = changes.begin(); it != changes.end(); ++it) value[it.key()] = it.value();
return value;
}
bool AnyContains(const std::vector<std::string>& lines, std::string_view part) {
return std::ranges::any_of(lines, [&](const std::string& line) { return line.contains(part); });
}
// The log a pool writes into from its threads.
struct Logs {
std::mutex lock;
std::vector<std::string> lines;
Pool::Log Sink() {
return [this](const std::string& line) {
const std::lock_guard guard(lock);
lines.push_back(line);
};
}
std::vector<std::string> Copy() {
const std::lock_guard guard(lock);
return lines;
}
void Clear() {
const std::lock_guard guard(lock);
lines.clear();
}
};
struct Sleeper {
std::int64_t pid = Process::Start({.argv = {"sleep", "300"}}).pid;
std::string key = std::format("{}:{}", pid, Platform::ProcStart(pid).value_or(0));
void Kill() const {
Process::Signal(pid, SIGKILL);
Process::Wait(pid);
}
};
double Seconds(std::chrono::steady_clock::time_point since) {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - since).count();
}
const Pool::Scheduler::OnDone NoFollowUp = [](const Json&, const Json&, const Json&) { return std::vector<Json> {}; };
void TestPool() {
std::println("pool: admission (stacks, memory), a no-room wait ends on the next completion, one pass per cold prefix goes first");
std::mutex lock;
std::map<std::string, std::int32_t> running = {{"F", 0}, {"S", 0}};
std::map<std::string, std::int32_t> peak = {{"F", 0}, {"S", 0}};
auto execJob = [&](const Json& job, std::int64_t) {
const std::string lens = job.at("lens").get<std::string>();
{
const std::lock_guard guard(lock);
peak[lens] = std::max(peak[lens], ++running[lens]);
}
std::this_thread::sleep_for(150ms);
{
const std::lock_guard guard(lock);
--running[lens];
}
return std::pair<Json, Json>({{"label", job.at("label")}, {"status", "ok"}}, nullptr);
};
std::vector<Json> slots;
for (const std::int32_t n : {1, 2}) slots.push_back({{"id", std::format("slot {}", n)}, {"appUrl", std::format("http://localhost:{}", n)}, {"apiUrls", Json::array()}, {"dbUrl", nullptr}, {"login", nullptr}, {"slot", true}});
Pool::Stacks stacks(slots);
auto plenty = []() -> std::optional<double> { return 1e6; };
Pool::Admission adm([](const Json&) { return 100.0; }, [](const Json& j) { return j.at("lens") == "F"; }, &stacks, 10'000.0, 0, plenty);
std::vector<Json> jobs;
for (std::int32_t i = 0; i < 6; ++i) jobs.push_back({{"label", std::format("f{}", i)}, {"lens", "F"}});
for (std::int32_t i = 0; i < 3; ++i) jobs.push_back({{"label", std::format("s{}", i)}, {"lens", "S"}});
Pool::Scheduler s({.concurrency = 8, .log = [](const std::string&) {}, .admission = &adm});
s.Run(jobs, execJob, NoFollowUp);
std::vector<std::string> free = stacks.Free();
std::ranges::sort(free);
Check("admission: the passes that need a stack never outnumber the stacks (2), the rest fill the pool, everything finishes, every stack is back",
s.DoneList().size() == 9 && peak["F"] == 2 && peak["S"] == 3 && free == std::vector<std::string>{"slot 1", "slot 2"} && adm.Inflight() == 0 && adm.Held() == 0,
std::format("F {} S {}", peak["F"], peak["S"]));
running = {{"F", 0}, {"S", 0}};
peak = {{"F", 0}, {"S", 0}};
Pool::Admission memory([](const Json&) { return 300.0; }, [](const Json&) { return false; }, nullptr, 1000.0, 0, plenty);
jobs.clear();
for (std::int32_t i = 0; i < 7; ++i) jobs.push_back({{"label", std::format("s{}", i)}, {"lens", "S"}});
Pool::Scheduler m({.concurrency = 8, .log = [](const std::string&) {}, .admission = &memory});
m.Run(jobs, execJob, NoFollowUp);
Check("admission: memory - passes at 300 MB in a 1000 MB budget run three at once, and all of them finish", m.DoneList().size() == 7 && peak["S"] == 3, std::format("S {}", peak["S"]));
Logs logs;
const double now = Platform::Now();
std::vector<Json> seat = {{{"id", "3"}, {"kind", "oauth"}, {"secret", "x"}, {"state", "ok"}, {"util", 80.0}, {"binding", "five_hour"}, {"resetsAt", nullptr},
{"windows", {{"five_hour", {{"utilization", 80.0}, {"resetsAt", now + 3600}}}, {"seven_day", {{"utilization", 30.0}, {"resetsAt", now + 86400}}}}},
{"pauseUntil", 0.0}, {"running", 0}, {"procs", 0}, {"spentUsd", 0.0}, {"error", nullptr}, {"probedWith", nullptr}}};
Json cfg = SeatPool::PoolConfig({});
cfg["lensCostPct"] = 2;
cfg["maxUtil"] = 85;
Pool::Scheduler r({.concurrency = 8, .log = logs.Sink(), .seats = &seat, .seatConfig = cfg, .apiConfig = SeatPool::ApiConfig({})});
const auto t0 = std::chrono::steady_clock::now();
jobs.clear();
for (std::int32_t i = 0; i < 3; ++i) jobs.push_back({{"label", std::format("r{}", i)}});
r.Run(jobs, [](const Json& j, std::int64_t) {
std::this_thread::sleep_for(200ms);
return std::pair<Json, Json>({{"label", j.at("label")}, {"status", "ok"}}, nullptr);
}, NoFollowUp);
const std::vector<std::string> waited = logs.Copy();
Check("a no-room wait ends on the next completion, not at the window reset",
r.DoneList().size() == 3 && Seconds(t0) < 5 && AnyContains(waited, "no seat has room") && AnyContains(waited, "seat room is re-checked") && r.PauseUntil() == 0, Dyn::Join(waited, " | "));
std::map<std::string, double> started;
std::map<std::string, double> answered;
logs.Clear();
Pool::Scheduler w({.concurrency = 8,
.log = logs.Sink(),
.cacheKey = [](Json& j) -> std::optional<std::string> {
const Json& k = Dyn::Get(j, "key");
return k.is_null() ? std::nullopt : std::optional<std::string>(k.get<std::string>());
},
.warmProbe = [&](const Json& j) {
const std::lock_guard guard(lock);
return answered.contains(j.at("label").get<std::string>());
},
.warmPoll = 0.02});
jobs.clear();
for (std::int32_t i = 0; i < 5; ++i) jobs.push_back({{"label", std::format("a{}", i)}, {"key", "A"}, {"respondAfter", i == 0 ? 0.3 : 0.05}});
jobs.push_back({{"label", "n0"}, {"key", nullptr}});
w.Run(jobs, [&](const Json& j, std::int64_t) {
const std::string label = j.at("label").get<std::string>();
{
const std::lock_guard guard(lock);
started[label] = Platform::Now();
}
std::this_thread::sleep_for(std::chrono::duration<double>(Dyn::Has(j, "respondAfter") ? j.at("respondAfter").get<double>() : 0.05));
{
const std::lock_guard guard(lock);
answered[label] = Platform::Now();
}
std::this_thread::sleep_for(300ms);
return std::pair<Json, Json>({{"label", j.at("label")}, {"status", "ok"}}, nullptr);
}, NoFollowUp);
const double first = answered.at("a0");
bool followed = true;
for (std::int32_t i = 1; i < 5; ++i) {
const double at = started.at(std::format("a{}", i));
followed = followed && first <= at && at < first + 0.3;
}
Check("warm-up: the first pass of a cold prefix starts alone, the others follow its first response, a pass without a key is never held",
w.DoneList().size() == 6 && followed && started.at("n0") < first && AnyContains(logs.Copy(), "a0 goes first"));
}
void TestSeatPool(const fs::path& td) {
std::println("seat pool: re-placement on another seat, Runner.run on a fake claude");
const Json acfg = SeatPool::ApiConfig({{"REVIEW_GATES_API_BUDGET_USD", "10"}, {"REVIEW_GATES_API_LENS_COST_USD", "1"}, {"REVIEW_GATES_API_CONCURRENCY", "2"}});
const Json cfg = Seats::ConfigFromEnv({});
Logs logs;
std::mutex lock;
std::vector<std::pair<std::string, std::string>> order;
std::vector<Json> pool = {SeatPool::NewSeat("api1", "api", "k"), SeatPool::NewSeat("1", "oauth", "t1")};
for (Json& s : pool) s["state"] = "ok";
SeatPool::Ledger ledger(td / "l2.json", acfg);
Pool::Scheduler sch({.concurrency = 4, .log = logs.Sink(), .seats = &pool, .seatConfig = cfg, .apiConfig = With(acfg, {{"budgetUsd", 100}}), .ledger = &ledger});
sch.Run({{{"label", "j1"}}}, [&](const Json& job, std::int64_t) {
{
const std::lock_guard guard(lock);
order.emplace_back(job.at("label").get<std::string>(), job.at("seat").get<std::string>());
}
if (job.at("seat") == "api1") return std::pair<Json, Json>({{"label", job.at("label")}, {"status", "credits"}, {"reason", "Your credit balance is too low"}, {"costUsd", 0.0}, {"rateLimit", Json::object()}}, nullptr);
return std::pair<Json, Json>({{"label", job.at("label")}, {"status", "ok"}, {"costUsd", 0.5}, {"rateLimit", {{"five_hour", {{"utilization", 30}, {"resetsAt", 9'999'999'999LL}}}}}}, nullptr);
}, NoFollowUp);
std::map<std::string, std::string> statuses;
for (const Pool::Done& d : sch.DoneList()) statuses[d.rec.at("label").get<std::string>()] = d.rec.at("status").get<std::string>();
Check("a process that fails on its seat is placed again on another; the first seat is out",
statuses == std::map<std::string, std::string>{{"j1", "ok"}} && pool[0].at("state") == "out" && !order.empty() && order.front().second == "api1" && order.back().second == "1");
const fs::path fake = td / "claude";
const fs::path argsf = td / "args.txt";
Write(fake, "#!/bin/sh\nprintf \"%s\\n\" \"$@\" > \"$FAKE_ARGS\"\nprintf \"%s\" \"$REVIEW_GATES_DB\" > \"$FAKE_ARGS.db\"\ncat > /dev/null\n"
"if [ -n \"$FAKE_MCP\" ]; then echo '{\"type\":\"system\",\"subtype\":\"init\",\"mcp_servers\":[{\"name\":\"playwright\",\"status\":\"connected\"}],\"tools\":[\"Read\",\"mcp__playwright__browser_snapshot\"]}'; "
"echo '{\"type\":\"assistant\",\"message\":{\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"t1\",\"name\":\"mcp__playwright__browser_snapshot\",\"input\":{}}]}}'; "
"echo '{\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"t1\",\"content\":\"- page\"}]}}'; "
"elif [ -n \"$FAKE_SQL\" ]; then echo '{\"type\":\"system\",\"subtype\":\"init\",\"mcp_servers\":[],\"tools\":[\"Read\",\"Bash\"]}'; printf \"%s\\n\" \"$FAKE_SQL\"; "
"else echo '{\"type\":\"system\",\"subtype\":\"init\",\"mcp_servers\":[],\"tools\":[\"Read\"]}'; fi\n"
"echo '{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\":\"ok\",\"structured_output\":{\"verdict\":\"SEND-READY\",\"findings\":[],\"notes\":[],\"coverage\":[]},"
"\"usage\":{\"input_tokens\":1,\"output_tokens\":1},\"total_cost_usd\":0.01}'\n");
fs::permissions(fake, fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec | fs::perms::others_read | fs::perms::others_exec);
setenv("FAKE_ARGS", argsf.c_str(), 1);
const fs::path rscratch = td / "rscratch";
const std::string cwd = fs::current_path().string();
Pool::Runner runner({.repo = cwd, .scratch = rscratch, .bin = fake.string(), .effort = "xhigh", .log = logs.Sink(), .dryRun = false,
.functional = {{"mcp", {{"command", "npx"}, {"args", nullptr}}}, {"extraTools", Json::array()}}, .environments = {{Fn, "browser"}}});
runner.SetSeats({SeatPool::NewSeat("api1", "api", "k1"), SeatPool::NewSeat("api2", "api", "k2")});
const fs::path sysf = td / "sys.md";
Write(sysf, "x");
const std::vector<std::string> trees = {"."};
auto args = [&] { return Dyn::Split(Read(argsf), "\n"); };
auto has = [](const std::vector<std::string>& list, std::string_view item) { return std::ranges::contains(list, item); };
auto [rec, raw] = runner.Run({{"label", "f1"}, {"model", "claude-opus-5-5"}, {"lensFile", "repo-bug-prover.md"}, {"kind", "block"}, {"seat", "api1"}, {"attempt", 1}}, "the prompt", sysf, "{}", trees, 20, 1);
std::vector<std::string> a = args();
Check("Runner.run on a fake claude: ok, the seat on the record, persisted, no --mcp-config for a static pass, environment none",
rec.at("status") == "ok" && rec.at("seat") == "api1" && !has(a, "--no-session-persistence") && has(a, "--session-id") && !has(a, "--mcp-config") && rec.at("mcp") == false && rec.at("environment") == "none",
DumpSpaced(rec));
std::tie(rec, raw) = runner.Run({{"label", "f2"}, {"model", "claude-opus-5-5"}, {"lensFile", Fn}, {"kind", "block"}, {"seat", "api2"}, {"attempt", 1}}, "the prompt", sysf, "{}", trees, 20, 1);
a = args();
Check("...a functional pass whose init line shows no connected server is an error (no browser = no functional evidence), with --mcp-config passed, its playwright output dir made and environment browser",
rec.at("mcp") == true && rec.at("environment") == "browser" && rec.at("status") == "error" && rec.at("reason").get<std::string>().contains("did not connect") && has(a, "--mcp-config")
&& fs::is_directory(rscratch / "playwright" / "f2"));
// The runner captured its environment at construction; the fake reads
// this one.
runner.Env()["FAKE_MCP"] = "1";
std::tie(rec, raw) = runner.Run({{"label", "f2"}, {"model", "claude-opus-5-5"}, {"lensFile", Fn}, {"kind", "block"}, {"seat", "api2"}, {"attempt", 1}}, "the prompt", sysf, "{}", trees, 20, 1);
runner.Env().erase("FAKE_MCP");
Check("...and ok when the server connected", rec.at("status") == "ok" && !raw.is_null());
const std::string sid1 = rec.at("sessionId").get<std::string>();
Write(rscratch / "seat-api2" / "projects" / "-home-repo" / (std::format("{}.jsonl", sid1)), "{}\n");
std::tie(rec, raw) = runner.Run({{"label", "f2"}, {"model", "claude-opus-5-5"}, {"lensFile", Fn}, {"kind", "block"}, {"seat", "api1"}, {"attempt", 2}, {"resumeSession", sid1}, {"resumeSeat", "api2"}},
"the prompt", sysf, "{}", trees, 20, 1);
a = args();
const auto resumeAt = std::ranges::find(a, "--resume");
Check("resumed pass: --resume <id>, the transcript carried to the new seat, the resume prompt",
rec.at("sessionId") == sid1 && resumeAt != a.end() && resumeAt + 1 != a.end() && *(resumeAt + 1) == sid1 && fs::exists(rscratch / "seat-api1" / "projects" / "-home-repo" / (std::format("{}.jsonl", sid1)))
&& Read(rscratch / "prompts" / "f2.txt") == SeatPool::ResumePrompt);
// A pass with the database under test: the broker's address and a token
// in its process alone, never the URL; and no evidence when no SQL call
// reached the database.
const std::string durl = "postgresql://postgres:hunter2@127.0.0.1:54322/postgres";
PgWire::Broker dbr;
dbr.Start();
Pool::Runner rdb({.repo = cwd, .scratch = td / "dscratch", .bin = fake.string(), .effort = "xhigh", .log = logs.Sink(), .dryRun = false,
.environments = {{"repo-bug-prover.md", "supabase"}}, .dbUrl = durl, .broker = &dbr});
rdb.SetSeats({SeatPool::NewSeat("api1", "api", "k1")});
auto sqlLines = [](const std::string& content) {
return std::format("{}\n{}", DumpSpaced({{"type", "assistant"},
{"message", {{"role", "assistant"}, {"content", Json::array({{{"type", "tool_use"}, {"id", "q1"}, {"name", "Bash"}, {"input", {{"command", "/app/bin/crafter-review sql <<'SQL'\nselect 1;\nSQL"}}}}})}}}}),
DumpSpaced({{"type", "user"}, {"message", {{"role", "user"}, {"content", Json::array({{{"type", "tool_result"}, {"tool_use_id", "q1"}, {"content", content}}})}}}}));
};
rdb.Env()["FAKE_SQL"] = sqlLines(std::format("{} - cannot connect to 127.0.0.1:54322", PgWire::NoDatabase));
std::tie(rec, raw) = rdb.Run({{"label", "d1"}, {"model", "claude-opus-5-5"}, {"lensFile", "repo-bug-prover.md"}, {"kind", "block"}, {"seat", "api1"}, {"attempt", 1}}, "the prompt", sysf, "{}", trees, 20, 1);
const std::string argsText = Read(argsf);
const std::string gotEnv = Read(std::format("{}.db", argsf.string()));
Check("Runner.run, supabase: the db pass's process gets the broker and its token (no URL, no password, on no command line); a pass whose every SQL call missed the database is an error with no result",
gotEnv == dbr.Address(durl) && dbr.UrlOf(gotEnv.substr(gotEnv.rfind('#') + 1)) == durl && !(gotEnv + argsText).contains("hunter2") && has(Dyn::Split(argsText, "\n"), "--safe-mode") && rec.at("status") == "error"
&& rec.at("reason").get<std::string>().contains("reached the database") && raw.is_null() && Dyn::Equal(rec.at("db"), {{"calls", 1}, {"answered", 0}}) && rec.at("environment") == "supabase",
DumpSpaced(rec));
rdb.Env()["FAKE_SQL"] = sqlLines(std::format("{} to postgres on 127.0.0.1:54322 as postgres - ONE transaction\n SELECT 1", PgWire::Answered));
std::tie(rec, raw) = rdb.Run({{"label", "d1"}, {"model", "claude-opus-5-5"}, {"lensFile", "repo-bug-prover.md"}, {"kind", "block"}, {"seat", "api1"}, {"attempt", 1}}, "the prompt", sysf, "{}", trees, 20, 1);
rdb.Env().erase("FAKE_SQL");
Check("...and ok once the database answered", rec.at("status") == "ok" && !raw.is_null() && Dyn::Equal(rec.at("db"), {{"calls", 1}, {"answered", 1}}));
std::tie(rec, raw) = rdb.Run({{"label", "d2"}, {"model", "claude-opus-5-5"}, {"lensFile", "repo-better-solution.md"}, {"kind", "block"}, {"seat", "api1"}, {"attempt", 1}}, "the prompt", sysf, "{}", trees, 20, 1);
Check("...while a lens without the database gets no broker at all", Read(std::format("{}.db", argsf.string())).empty() && rec.at("status") == "ok" && rec.at("environment") == "none");
dbr.Close();
unsetenv("FAKE_ARGS");
}
void TestDrivers(const fs::path& td) {
std::println("several drivers at once: the pool against the board - a wait other drivers hold, the machine's cap, stacks and memory across drivers");
const fs::path path = td / "seat-board.json";
Logs logs;
const double now = Platform::Now();
const Json cfg = SeatPool::PoolConfig({});
const Json acfg = SeatPool::ApiConfig({{"REVIEW_GATES_API_CONCURRENCY", "8"}, {"REVIEW_GATES_API_LENS_COST_USD", "1"}});
auto mk = [&](const std::string& id, const std::string& secret, std::int64_t fh, std::int64_t sd) {
Json s = SeatPool::NewSeat(id, "oauth", secret);
s["state"] = "ok";
s["windows"] = {{"five_hour", {{"utilization", fh}, {"resetsAt", now + 3600}}}, {"seven_day", {{"utilization", sd}, {"resetsAt", now + 86400}}}};
const auto [util, binding] = Seats::SeatUtil(s.at("windows"));
s["util"] = util;
s["binding"] = binding;
return s;
};
std::deque<Json> jobs;
std::mutex jobsLock;
auto chooserFor = [&](const std::string& id, const std::string& label, const Json& extra = Json::object()) {
return SeatPool::Board::Chooser([&jobs, &jobsLock, id, label, extra](const std::vector<Json*>& ranked, const SeatPool::Others&) -> std::optional<std::pair<Json*, Json*>> {
for (Json* s : ranked) {
if (s->at("id") == id) {
const std::lock_guard guard(jobsLock);
jobs.push_back(With({{"label", label}, {"timeoutMin", 20}}, extra));
return std::pair(s, &jobs.back());
}
}
return std::nullopt;
});
};
auto place = [&](SeatPool::Board& board, Json& seat, const Json& c, const SeatPool::Board::Chooser& chooser) {
std::vector<Json> one = {seat};
board.Place(one, now, c, acfg, chooser);
seat = one.front();
};
SeatPool::Board A(path, {}, std::nullopt, logs.Sink());
A.Join({{"run", "A"}}, now);
const Sleeper child2;
SeatPool::Board D(path, child2.key, std::nullopt, logs.Sink());
D.Join({{"run", "D"}}, now);
const Json cfg3 = With(cfg, {{"lensCostPct", 2}, {"maxUtil", 85}});
std::vector<Json> seat3 = {mk("3", "t3", 80, 30)};
Json sd3 = mk("3", "t3", 80, 30);
place(D, sd3, cfg3, chooserFor("3", "h0"));
// 80 + 4 held: 80 + 4 + 2 > 85, no room for A until D's passes finish.
place(D, sd3, cfg3, chooserFor("3", "h1"));
auto execJob = [&](const Json& job, std::int64_t) {
std::this_thread::sleep_for(100ms);
return std::pair<Json, Json>({{"label", job.at("label")}, {"status", "ok"}, {"rateLimit", {{"five_hour", {{"utilization", 81}, {"resetsAt", now + 3600}}}}}}, nullptr);
};
{
std::jthread freer([&] {
std::this_thread::sleep_for(500ms);
D.Finish(sd3, "h0", Platform::Now());
D.Finish(sd3, "h1", Platform::Now());
});
logs.Clear();
Pool::Scheduler sch({.concurrency = 8, .log = logs.Sink(), .seats = &seat3, .seatConfig = cfg3, .apiConfig = acfg, .board = &A, .noRoomWait = 0.2});
const auto t0 = std::chrono::steady_clock::now();
sch.Run({{{"label", "a0"}, {"timeoutMin", 20}}}, execJob, NoFollowUp);
const double dt = Seconds(t0);
const Json bd = Dyn::LoadJsonFile(path);
const Json& inflight = bd.at("seats").at(seat3[0].at("boardKey").get<std::string>()).at("inflight");
bool mine = false;
for (auto it = inflight.begin(); it != inflight.end(); ++it) mine = mine || it.key().starts_with(A.Me());
const std::vector<std::string> lines = logs.Copy();
Check("a no-room wait held by another driver's passes ends when they finish, not at the window reset",
sch.DoneList().size() == 1 && dt < 5 && AnyContains(lines, "hold the room") && !mine, std::format("{:.1f}s {}", dt, Dyn::Join(lines, " | ")));
Check("...and the finished pass's reading is on the board", bd.at("seats").at(seat3[0].at("boardKey").get<std::string>()).at("windows").at("five_hour").at("utilization") == 81);
}
place(D, sd3, cfg3, chooserFor("3", "w0"));
// Two of D's on the machine: a width of 2 holds A's pass until one
// finishes.
place(D, sd3, cfg3, chooserFor("3", "w1"));
{
std::jthread freer([&] {
std::this_thread::sleep_for(500ms);
D.Finish(sd3, "w0", Platform::Now());
});
logs.Clear();
std::vector<Json> seat3b = {mk("3", "t3", 10, 30)};
Pool::Scheduler sch({.concurrency = 2, .log = logs.Sink(), .seats = &seat3b, .seatConfig = cfg3, .apiConfig = acfg, .board = &A, .noRoomWait = 0.2});
const auto t0 = std::chrono::steady_clock::now();
sch.Run({{{"label", "a1"}, {"timeoutMin", 20}}}, execJob, NoFollowUp);
const double dt = Seconds(t0);
const std::vector<std::string> lines = logs.Copy();
Check("the machine-wide cap holds a launch while other drivers fill it, and releases it when one of theirs finishes",
sch.DoneList().size() == 1 && 0.4 < dt && dt < 5 && AnyContains(lines, "the cap is 2"), std::format("{:.1f}s {}", dt, Dyn::Join(lines, " | ")));
}
D.Finish(sd3, "w1", Platform::Now());
// Memory and stacks across drivers (2026-10-31).
Check("stack keys: one application whatever the spelling (localhost/127.0.0.1, case, trailing slash, the default port); another port another stack; no URL, its id",
Pool::StackKey({{"id", "a"}, {"appUrl", "http://localhost:5173/"}}) == Pool::StackKey({{"id", "b"}, {"appUrl", "HTTP://127.0.0.1:5173"}})
&& Pool::StackKey({{"id", "b"}, {"appUrl", "HTTP://127.0.0.1:5173"}}) == "http://127.0.0.1:5173" && Pool::StackKey({{"id", "c"}, {"appUrl", "http://localhost"}}) == "http://127.0.0.1:80"
&& Pool::StackKey({{"id", "d"}, {"appUrl", "http://localhost:5273"}}) != Pool::StackKey({{"id", "a"}, {"appUrl", "http://localhost:5173"}}) && Pool::StackKey({{"id", "s1"}}) == "s1");
// What the board's fold left in A.others when another driver had two
// passes in flight (test_drivers checks the fold itself).
SeatPool::Others held;
held.count = 2;
held.mb = 1050.0;
held.stacks = {"http://127.0.0.1:5173"};
auto plenty = []() -> std::optional<double> { return 1e6; };
Pool::Stacks stx({{{"id", "slot 1"}, {"appUrl", "http://localhost:5173/"}}, {{"id", "slot 2"}, {"appUrl", "http://localhost:5273"}}});
Pool::Admission adm([](const Json& j) { return j.at("mb").get<double>(); }, [](const Json& j) { return Dyn::Truthy(Dyn::Get(j, "app")); }, &stx, 2000.0, 0, plenty, logs.Sink());
adm.SetOthers(held);
Json j1 = {{"label", "x"}, {"mb", 840}, {"app", true}};
const bool ok = adm.Can(j1);
adm.Take(j1);
Check("Admission: a stack another driver holds is not free here - the pass takes the other; their memory counts against the budget beside this driver's (1050 + 840 + 200 > 2000, + 100 fits)",
ok && j1.at("stack").at("id") == "slot 2" && j1.at("stackKey") == "http://127.0.0.1:5273" && !adm.Can({{"label", "y"}, {"mb", 840}, {"app", true}}) && !adm.Can({{"label", "z"}, {"mb", 200}})
&& adm.Can({{"label", "z"}, {"mb", 100}}),
DumpSpaced(Dyn::Get(j1, "stack")));
adm.Give(j1);
logs.Clear();
const Json big = {{"label", "b"}, {"mb", 5000}};
const bool waits = !adm.Can(big);
adm.SetOthers(SeatPool::Others());
Check("Admission: nothing of this driver's in flight but other drivers' passes on the machine - a pass over the budget waits for them instead of running alone; with the machine idle it runs alone",
waits && adm.Can(big) && !j1.contains("stackKey") && logs.Copy().empty());
// The pool: a stack another driver holds - the pass waits (re-checked,
// not spun on) and runs when theirs finishes.
Json sE = mk("1", "t1", 10, 50);
place(D, sE, cfg, chooserFor("1", "hold", {{"_admitted", 840.0}, {"stackKey", "http://127.0.0.1:5173"}}));
Pool::Stacks stw({{{"id", "slot 1"}, {"appUrl", "http://localhost:5173"}}});
Pool::Admission admw([](const Json&) { return 100.0; }, [](const Json&) { return true; }, &stw, 10'000.0, 0, plenty, logs.Sink());
double seenAt = 0;
Json seenEntry;
const std::string keyE = sE.at("boardKey").get<std::string>();
auto execBoard = [&](const Json& job, std::int64_t) {
seenAt = Platform::Now();
seenEntry = Dyn::Get(Dyn::LoadJsonFile(path).at("seats").at(keyE).at("inflight"), std::format("{}/{}", A.Me(), job.at("label").get<std::string>()));
std::this_thread::sleep_for(100ms);
return std::pair<Json, Json>({{"label", job.at("label")}, {"status", "ok"}}, nullptr);
};
{
std::jthread freer([&] {
std::this_thread::sleep_for(600ms);
D.Finish(sE, "hold", Platform::Now());
});
logs.Clear();
std::vector<Json> seats = {mk("1", "t1", 10, 50)};
Pool::Scheduler sch({.concurrency = 8, .log = logs.Sink(), .seats = &seats, .seatConfig = cfg, .apiConfig = acfg, .admission = &admw, .board = &A, .noRoomWait = 0.2});
const double t0 = Platform::Now();
sch.Run({{{"label", "w0"}, {"timeoutMin", 20}}}, execBoard, NoFollowUp);
const Json bd = Dyn::LoadJsonFile(path);
bool mine = false;
for (const Json& s : bd.at("seats")) {
for (auto it = Dyn::Or(Dyn::Get(s, "inflight"), Dyn::EmptyDict()).begin(); it != Dyn::Or(Dyn::Get(s, "inflight"), Dyn::EmptyDict()).end(); ++it) mine = mine || it.key().starts_with(A.Me());
}
const std::vector<std::string> lines = logs.Copy();
const auto once = std::ranges::count_if(lines, [](const std::string& l) { return l.contains("nothing can launch"); });
Check("the pool: a pass whose only stack another driver holds waits, re-checked every no_room_wait and logged once, and launches when theirs finishes - on the board with its memory and its stack",
sch.DoneList().size() == 1 && 0.5 < seenAt - t0 && seenAt - t0 < 5 && once == 1 && AnyContains(lines, "stack(s) http://127.0.0.1:5173") && Dyn::Equal(Dyn::Get(seenEntry, "mb"), 100.0)
&& Dyn::Get(seenEntry, "stack") == "http://127.0.0.1:5173" && stw.Can() && !mine,
std::format("{:.2f}s {} {}", seenAt - t0, DumpSpaced(seenEntry), Dyn::Join(lines, " | ")));
}
// Two drivers racing for the last stack: one gets it.
const Sleeper child3;
SeatPool::Board F(path, child3.key, std::nullopt, logs.Sink());
F.Join({{"run", "F"}}, now);
std::mutex picksLock;
std::vector<bool> picks;
auto racer = [&](SeatPool::Board& board, const std::string& label) {
Pool::Stacks st({{{"id", "slot 1"}, {"appUrl", "http://localhost:5999"}}});
Pool::Admission ad([](const Json&) { return 100.0; }, [](const Json&) { return true; }, &st, 10'000.0, 0, plenty);
Json job = {{"label", label}, {"timeoutMin", 20}};
std::vector<Json> one = {mk("1", "t1", 10, 50)};
const SeatPool::Board::Placed got = board.Place(one, now, cfg, acfg, [&](const std::vector<Json*>& cands, const SeatPool::Others& others) -> std::optional<std::pair<Json*, Json*>> {
ad.SetOthers(others);
std::this_thread::sleep_for(50ms);
if (!ad.Can(job)) return std::nullopt;
ad.Take(job);
return std::pair(cands.front(), &job);
});
const std::lock_guard guard(picksLock);
picks.push_back(got.picked.has_value());
};
{
const std::jthread ta(racer, std::ref(A), std::string("ra"));
const std::jthread tf(racer, std::ref(F), std::string("rf"));
}
Check("two drivers racing for the last stack on the machine: exactly one gets it", std::ranges::count(picks, true) == 1);
F.Leave();
child3.Kill();
A.Leave();
child2.Kill();
}
}
int main(int argc, char** argv) {
const std::vector<std::string_view> args(argv, argv + argc);
if (args.size() > 1 && args[1] == Process::SuperviseVerb) return Process::SuperviseMain(std::span(args).subspan(2));
Platform::SetMode(Platform::Mode::Native);
const fs::path td = fs::temp_directory_path() / std::format("crafter-review-scheduler-{}", Platform::PublishedPid());
fs::remove_all(td);
fs::create_directories(td);
// No check reaches the machine's own board: the drivers running on it are
// real.
setenv("REVIEW_GATES_BOARD", (td / "seat-board.json").c_str(), 1);
try {
TestPool();
TestSeatPool(td);
TestDrivers(td);
} catch (const std::exception& error) {
std::println(std::cerr, "scheduler: {}", error.what());
return 1;
}
fs::remove_all(td);
return Failures == 0 ? 0 : 1;
}