test(run): the store rules, stacks and memory, and the review fixes, check by check

StoreRules (23 checks), StacksMemory (24) and ReviewFixes (19), each a
test of its own.

Run and SlotsPlan now have seams that start as the real ones: appReady,
probe, query, bypassesRls, migrationsAtHead, migrationsDrift, treeDrift,
promptFor, runPass, resumable, instrumentPreflight, and a Machine for
SlotsPlan. MigrationsAtHead is split out of MigrationsDrift for it; the
HEAD side is still worked out once per tree and base.

The Postgres stand-in and the raw broker call move to tests/Common, shared
by PgWire and ReviewFixes. ReviewFixes runs this binary as `sql`, and a
real server that resets every connection makes the startup message either
fail to go out or never get its answer - a DbError either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 09:39:53 +01:00
commit dd0929b705
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
8 changed files with 1328 additions and 185 deletions

View file

@ -215,7 +215,8 @@ namespace Crafter::Review::Run {
}
}
std::pair<std::int64_t, std::vector<std::string>> SlotsPlan(const Json& cfg, const fs::path& repo, const std::optional<fs::path>& artifact, const std::optional<fs::path>& scratchRoot, const Log& given) {
std::pair<std::int64_t, std::vector<std::string>> SlotsPlan(const Json& cfg, const fs::path& repo, const std::optional<fs::path>& artifact, const std::optional<fs::path>& scratchRoot, const Log& given,
const Machine& machine) {
const Log log = given ? given : Log([](const std::string& line) { std::println("{}", line); });
const Json& sl = cfg.at("slots");
if (!Dyn::Truthy(Dyn::Get(sl, "dir"))) {
@ -244,8 +245,8 @@ namespace Crafter::Review::Run {
if (!best || Number(cost.megabytes) > Number(best->megabytes)) best = std::move(cost);
}
const double passMb = Number(best->megabytes);
const std::optional<std::int64_t> avail = Memory::Available();
const std::int64_t reserve = Memory::Reserve();
const std::optional<std::int64_t> avail = machine.available ? machine.available() : Memory::Available();
const std::int64_t reserve = machine.reserve ? machine.reserve() : Memory::Reserve();
const auto upCount = static_cast<std::int64_t>(up.size());
if (!avail) {
log("slots: no /proc/meminfo - cannot size the slots by memory");
@ -308,6 +309,19 @@ namespace Crafter::Review::Run {
// ---- the Run ---------------------------------------------------------------------------
Run::Run() : repo(fs::current_path()), t0(Platform::Now()) {
log = [this](const std::string& message) { Message(message); };
appReady = [](std::string_view url, std::string_view path) { return AppReady(url, path); };
probe = [](std::string_view url) { return PgWire::Probe(url); };
query = [](std::string_view url, std::string_view sql) { return PgWire::Query(url, sql); };
bypassesRls = [](std::string_view url) { return PgWire::BypassesRls(url); };
migrationsAtHead = [this] { return MigrationsAtHead(); };
migrationsDrift = [this](const std::string& url) { return MigrationsDrift(url); };
treeDrift = [this] { return TreeDrift(); };
promptFor = [this](const Json& job) { return PromptFor(job); };
runPass = [this](const Json& job, const std::string& prompt, const fs::path& systemPath, std::string_view schema, std::int64_t maxTurns, std::int64_t timeoutMin, bool persist) {
return runner->Run(job, prompt, systemPath, schema, trees, maxTurns, timeoutMin, persist);
};
resumable = [this](const std::string& name, const std::string& lensFile, std::string_view title) { return Resumable(name, lensFile, title); };
instrumentPreflight = [this](std::span<const std::string> lensFiles) { InstrumentPreflight(lensFiles); };
}
Run::Run(Arguments given, Json config) : Run() {
@ -368,7 +382,7 @@ namespace Crafter::Review::Run {
for (const std::string& t : trees) {
if (!Artifact::IsToplevel(t)) Die(std::format("--trees: {} is not the toplevel of a git checkout", t));
}
if (const std::optional<std::string> drift = TreeDrift()) Die(std::format("{} - re-freeze, or check out the frozen worktree (freeze --worktree)", *drift));
if (const std::optional<std::string> drift = treeDrift()) Die(std::format("{} - re-freeze, or check out the frozen worktree (freeze --worktree)", *drift));
gateDir = Dyn::JoinPath(a.artifact, "gate-state");
fs::create_directories(gateDir);
Inventory::Built built;
@ -738,41 +752,45 @@ namespace Crafter::Review::Run {
return Dyn::Split(base, "_", 1).front();
}
MigrationsHead Run::MigrationsAtHead() {
constexpr std::string_view M = "supabase/migrations/";
auto git = [&](std::vector<std::string> args) {
std::vector<std::string> argv = {"git", "-C", trees.front()};
std::ranges::move(args, std::back_inserter(argv));
return Process::Run(argv).out;
};
std::map<std::string, std::string> atHead;
for (const std::string& f : Dyn::Split(git({"ls-tree", "--name-only", "HEAD", std::string(M)}))) {
if (const std::optional<std::string> v = MigrationVersion(f)) atHead[*v] = f;
}
std::vector<std::string> inRange;
if (!a.base.empty() && !atHead.empty()) {
for (const std::string& f : Dyn::Split(git({"diff", "--name-only", "--diff-filter=AM", std::format("{}..HEAD", a.base), "--", std::string(M)}))) {
if (MigrationVersion(f)) inRange.push_back(f);
}
}
std::map<std::string, std::string> headFp;
for (const std::string& f : inRange) headFp[f] = PgWire::Fingerprint(git({"show", std::format("HEAD:{}", f)}));
return {atHead, inRange, headFp};
}
std::pair<std::optional<std::string>, std::optional<std::string>> Run::MigrationsDrift(const std::string& url) {
// The HEAD side is the same for every stack: worked out once per tree
// and base.
// and base (a tree's HEAD does not move during a run - TreeDrift
// aborts it first).
constexpr std::string_view M = "supabase/migrations/";
const std::pair<std::string, std::string> key = {trees.front(), a.base};
if (!migCache.contains(key)) {
auto git = [&](std::vector<std::string> args) {
std::vector<std::string> argv = {"git", "-C", trees.front()};
std::ranges::move(args, std::back_inserter(argv));
return Process::Run(argv).out;
};
std::map<std::string, std::string> atHead;
for (const std::string& f : Dyn::Split(git({"ls-tree", "--name-only", "HEAD", std::string(M)}))) {
if (const std::optional<std::string> v = MigrationVersion(f)) atHead[*v] = f;
}
std::vector<std::string> inRange;
if (!a.base.empty() && !atHead.empty()) {
for (const std::string& f : Dyn::Split(git({"diff", "--name-only", "--diff-filter=AM", std::format("{}..HEAD", a.base), "--", std::string(M)}))) {
if (MigrationVersion(f)) inRange.push_back(f);
}
}
std::map<std::string, std::string> headFp;
for (const std::string& f : inRange) headFp[f] = PgWire::Fingerprint(git({"show", std::format("HEAD:{}", f)}));
migCache[key] = {atHead, inRange, headFp};
}
if (!migCache.contains(key)) migCache[key] = migrationsAtHead();
const auto& [atHead, inRange, headFp] = migCache.at(key);
if (atHead.empty()) return {std::nullopt, std::format("no {} at the range HEAD - applied migrations not compared", M)};
std::vector<std::vector<std::optional<std::string>>> rows;
try {
try {
rows = PgWire::Query(url, "select version, array_to_string(statements, ' ') from supabase_migrations.schema_migrations").second;
rows = query(url, "select version, array_to_string(statements, ' ') from supabase_migrations.schema_migrations").second;
} catch (const PgWire::SqlError& e) {
// No statements column (an old CLI): versions alone.
if (!e.fields.contains('C') || e.fields.at('C') != "42703") throw;
rows = PgWire::Query(url, "select version, null from supabase_migrations.schema_migrations").second;
rows = query(url, "select version, null from supabase_migrations.schema_migrations").second;
}
} catch (const PgWire::SqlError& e) {
const std::string code = e.fields.contains('C') ? e.fields.at('C') : std::string();
@ -912,7 +930,7 @@ namespace Crafter::Review::Run {
if (!app.empty()) apps = stacks->All();
for (const Json& s : apps) {
const Json& readyPath = Dyn::Get(fcfg, "readyPath");
const auto [ok, detail] = AppReady(Dyn::Str(s.at("appUrl")), Dyn::Truthy(readyPath) ? Dyn::Str(readyPath) : std::string("/"));
const auto [ok, detail] = appReady(Dyn::Str(s.at("appUrl")), Dyn::Truthy(readyPath) ? Dyn::Str(readyPath) : std::string("/"));
if (!ok) {
const Json& how = Dyn::Truthy(Dyn::Get(s, "slot")) ? Dyn::Get(sl, "start") : Dyn::Get(fcfg, "start");
Die(std::format("stacks ({}): {}: the application does not answer ({}) - start it from the frozen tree first{}", who, Dyn::Str(s.at("id")), detail,
@ -924,18 +942,18 @@ namespace Crafter::Review::Run {
std::optional<std::string> note;
for (const Json& s : dbs) {
const std::string url = Dyn::Str(s.at("dbUrl"));
const auto [ok, detail] = PgWire::Probe(url);
const auto [ok, detail] = probe(url);
if (!ok) {
const Json& supabaseStart = Dyn::Get(cfg.at("supabase"), "start");
const Json& how = Dyn::Truthy(Dyn::Get(s, "slot")) ? Dyn::Get(sl, "start") : (Dyn::Truthy(supabaseStart) ? supabaseStart : Dyn::Get(fcfg, "start"));
Die(std::format("stacks ({}): {}: the database does not answer ({}) - start the stack from the frozen tree first{}", who, Dyn::Str(s.at("id")), detail,
Dyn::Truthy(how) ? std::format(" ({})", Dyn::Str(how)) : std::string()));
}
const auto [drift, n] = MigrationsDrift(url);
const auto [drift, n] = migrationsDrift(url);
if (drift) Die(std::format("stacks ({}): {}: the database: {}", who, Dyn::Str(s.at("id")), *drift));
if (!firstDetail || firstDetail->empty()) firstDetail = detail;
if (!note || note->empty()) note = n;
bypass.insert(PgWire::BypassesRls(url));
bypass.insert(bypassesRls(url));
}
if (!dbs.empty()) {
dbChecked = dbs.size() == 1 ? std::format("it answers ({}); {}", firstDetail.value_or("None"), note.value_or("None"))
@ -978,7 +996,7 @@ namespace Crafter::Review::Run {
}
if (email.empty() || PgWire::CheckUrl(Dyn::Get(stack, "dbUrl"))) return std::nullopt;
try {
const auto [columns, rows] = PgWire::Query(Dyn::Str(stack.at("dbUrl")), std::format("select 1 from auth.users where lower(email) = '{}'", Dyn::Replace(email, "'", "''")));
const auto [columns, rows] = query(Dyn::Str(stack.at("dbUrl")), std::format("select 1 from auth.users where lower(email) = '{}'", Dyn::Replace(email, "'", "''")));
return rows.empty() ? std::optional<std::string>(email) : std::nullopt;
} catch (const PgWire::SqlError&) {
return std::nullopt;
@ -1283,14 +1301,14 @@ namespace Crafter::Review::Run {
abortPool(std::format("artifact changed during the run ({} != {})", now.substr(0, 8), h8));
return {{{"label", job.at("label")}, {"status", "error"}, {"reason", "artifact changed during the run - aborted"}}, nullptr};
}
if (const std::optional<std::string> drift = TreeDrift()) {
if (const std::optional<std::string> drift = treeDrift()) {
abortPool(std::format("evidence drift during the run ({})", *drift));
return {{{"label", job.at("label")}, {"status", "error"}, {"reason", std::format("{} - aborted", *drift)}}, nullptr};
}
const auto [systemPath, prompt] = PromptFor(job);
const auto [systemPath, prompt] = promptFor(job);
const bool whole = job.at("kind") == "whole";
auto [rec, raw] = runner->Run(job, prompt, systemPath, schemas.at(whole ? "whole" : "block"), trees, Dyn::Has(job, "maxTurns") ? Int(job.at("maxTurns")) : 20,
Dyn::Has(job, "timeoutMin") ? Int(job.at("timeoutMin")) : 20, whole);
auto [rec, raw] = runPass(job, prompt, systemPath, schemas.at(whole ? "whole" : "block"), Dyn::Has(job, "maxTurns") ? Int(job.at("maxTurns")) : 20,
Dyn::Has(job, "timeoutMin") ? Int(job.at("timeoutMin")) : 20, whole);
if (Dyn::Truthy(Dyn::Get(job, "stack"))) rec["stack"] = job.at("stack").at("id");
if (Dyn::Truthy(Dyn::Get(rec, "memPeakMb"))) {
const Json& configured = Dyn::Get(environments, Dyn::Str(job.at("lensFile")));
@ -1346,7 +1364,7 @@ namespace Crafter::Review::Run {
std::vector<Json> resumed;
std::vector<std::string> live;
for (const std::string& lf : lensFiles) {
if (std::optional<Json> r = Resumable(Plan::LensName(lf), lf, title)) {
if (std::optional<Json> r = resumable(Plan::LensName(lf), lf, title)) {
resumed.push_back(std::move(*r));
} else {
live.push_back(lf);
@ -1356,7 +1374,7 @@ namespace Crafter::Review::Run {
// Nothing runs: no stack, database or application is needed for a
// result already bought.
if (lensFiles.empty()) return resumed;
InstrumentPreflight(lensFiles);
instrumentPreflight(lensFiles);
std::map<std::string, std::vector<Json>> parts;
std::map<std::string, std::set<std::string>> missing;
std::map<std::string, std::set<std::string>> planned;

View file

@ -76,11 +76,21 @@ export namespace Crafter::Review::Run {
// Below 500 is ok.
std::pair<bool, std::string> AppReady(std::string_view url, std::string_view path = "/", double timeout = 10);
// The machine's memory as SlotsPlan reads it (the tests give their own).
struct Machine {
std::function<std::optional<std::int64_t>()> available;
std::function<std::int64_t()> reserve;
};
// `slots`: how many stacks to bring up - as many as free memory holds,
// and no more than the range's browser passes can use at once - and the
// commands for the ones not up yet. (want, commands).
std::pair<std::int64_t, std::vector<std::string>> SlotsPlan(const Json& cfg, const std::filesystem::path& repo, const std::optional<std::filesystem::path>& artifact = std::nullopt,
const std::optional<std::filesystem::path>& scratchRoot = std::nullopt, const Log& log = {});
const std::optional<std::filesystem::path>& scratchRoot = std::nullopt, const Log& log = {}, const Machine& machine = {});
// ({version: path} of the migrations at the range HEAD, the range's added
// or changed migrations, {path: fingerprint at HEAD} of those).
using MigrationsHead = std::tuple<std::map<std::string, std::string>, std::vector<std::string>, std::map<std::string, std::string>>;
using QueryResult = std::pair<std::vector<std::string>, std::vector<std::vector<std::optional<std::string>>>>;
class Run {
public:
@ -108,6 +118,7 @@ export namespace Crafter::Review::Run {
std::map<std::string, std::string> DbLenses(std::span<const std::string> lensFiles) const;
void InstrumentPreflight(std::span<const std::string> lensFiles);
static std::optional<std::string> MigrationVersion(std::string_view file);
MigrationsHead MigrationsAtHead();
// (problem, note): the migrations at the range HEAD against the ones
// the database applied.
std::pair<std::optional<std::string>, std::optional<std::string>> MigrationsDrift(const std::string& url);
@ -226,10 +237,26 @@ export namespace Crafter::Review::Run {
std::vector<Json> seats;
std::map<std::filesystem::path, Json> envMarkers;
std::set<std::pair<std::string, std::string>> envSkips;
std::map<std::pair<std::string, std::string>, std::tuple<std::map<std::string, std::string>, std::vector<std::string>, std::map<std::string, std::string>>> migCache;
std::map<std::pair<std::string, std::string>, MigrationsHead> migCache;
// The log every line goes to (the tests put their own here).
Log log;
// ---- the seams a test replaces ---------------------------------------------
// Each starts as the real one; the methods call through these.
std::function<std::pair<bool, std::string>(std::string_view url, std::string_view path)> appReady;
std::function<std::pair<bool, std::string>(std::string_view url)> probe;
std::function<QueryResult(std::string_view url, std::string_view sql)> query;
std::function<std::optional<bool>(std::string_view url)> bypassesRls;
std::function<MigrationsHead()> migrationsAtHead;
std::function<std::pair<std::optional<std::string>, std::optional<std::string>>(const std::string& url)> migrationsDrift;
std::function<std::optional<std::string>()> treeDrift;
std::function<std::pair<std::filesystem::path, std::string>(const Json& job)> promptFor;
std::function<std::pair<Json, Json>(const Json& job, const std::string& prompt, const std::filesystem::path& systemPath, std::string_view schema, std::int64_t maxTurns, std::int64_t timeoutMin,
bool persist)>
runPass;
std::function<std::optional<Json>(const std::string& name, const std::string& lensFile, std::string_view title)> resumable;
std::function<void(std::span<const std::string> lensFiles)> instrumentPreflight;
private:
// What exec_job's threads share with the pool's: the system files,
// the lens layers, the jobs run.

View file

@ -84,7 +84,7 @@ namespace {
// One per directory under tests/, each an ordinary main() that links the
// library: exit 0 passes, nonzero fails, 77 skips.
constexpr std::array<std::string_view, 20> Tests = {
constexpr std::array<std::string_view, 23> Tests = {
"Crypto",
"Text",
"Platform",
@ -105,6 +105,9 @@ namespace {
"OpenCode",
"Runner",
"Scheduler",
"StoreRules",
"StacksMemory",
"ReviewFixes",
};
// The version the binary reports: CI sets it from the tag, a checkout

155
tests/Common/FakePg.hpp Normal file
View file

@ -0,0 +1,155 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// A Postgres stand-in on 127.0.0.1 for the tests that talk the wire protocol
// (PgWire, ReviewFixes), and a raw call of the broker. Included after
// `import std;`, `import Crafter.Review;` and the socket headers.
namespace Testing {
std::string Be32(std::uint32_t v) {
return {static_cast<char>(v >> 24), static_cast<char>((v >> 16) & 0xFF), static_cast<char>((v >> 8) & 0xFF), static_cast<char>(v & 0xFF)};
}
std::string Msg(char type, const std::string& body = {}) {
return std::format("{}{}{}", type, Be32(static_cast<std::uint32_t>(body.size() + 4)), body);
}
std::string Err(const std::string& code, const std::string& text) {
return Msg('E', std::format("SERROR{}C{}{}M{}{}{}", std::string(1, '\0'), code, std::string(1, '\0'), text, std::string(1, '\0'), std::string(1, '\0')));
}
// A raw call of the broker on 127.0.0.1: the bytes as they are, the
// write side shut, everything that comes back.
std::string BrokerCall(std::uint16_t port, std::string_view bytes) {
const std::int32_t s = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = htons(port);
connect(s, reinterpret_cast<sockaddr*>(&address), sizeof(address));
::send(s, bytes.data(), bytes.size(), MSG_NOSIGNAL);
shutdown(s, SHUT_WR);
std::string all;
std::array<char, 4096> chunk {};
ssize_t got = 0;
while ((got = recv(s, chunk.data(), chunk.size(), 0)) > 0) all.append(chunk.data(), static_cast<std::size_t>(got));
close(s);
return all;
}
// A Postgres stand-in on 127.0.0.1 for one connection: cleartext
// password, simple Query, Parse/Bind/Describe/Execute/Sync. It records
// what it is sent and answers as the server does where the helper relies
// on it: two commands in one Parse are an error; COMMIT ends the
// transaction (status I); a statement after an error fails.
class FakePg {
public:
explicit FakePg(std::string password = "pw") : password(std::move(password)) {
listener = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
bind(listener, reinterpret_cast<sockaddr*>(&address), sizeof(address));
listen(listener, 1);
socklen_t length = sizeof(address);
getsockname(listener, reinterpret_cast<sockaddr*>(&address), &length);
port = ntohs(address.sin_port);
thread = std::thread([this] { Serve(); });
}
~FakePg() {
Served();
}
// The log once the connection is over.
std::vector<std::pair<std::string, std::string>> Served() {
if (thread.joinable()) thread.join();
return log;
}
std::uint16_t port = 0;
private:
std::string password;
std::int32_t listener = -1;
std::thread thread;
std::vector<std::pair<std::string, std::string>> log;
void Serve() {
const std::int32_t c = accept(listener, nullptr, nullptr);
std::string buffer;
auto read = [&](std::size_t n) {
while (buffer.size() < n) {
std::array<char, 4096> chunk {};
const ssize_t got = recv(c, chunk.data(), chunk.size(), 0);
if (got <= 0) break;
buffer.append(chunk.data(), static_cast<std::size_t>(got));
}
const std::string out = buffer.substr(0, std::min(n, buffer.size()));
buffer.erase(0, out.size());
return out;
};
auto length = [](const std::string& bytes) {
return (static_cast<std::uint32_t>(static_cast<std::uint8_t>(bytes[0])) << 24) | (static_cast<std::uint32_t>(static_cast<std::uint8_t>(bytes[1])) << 16)
| (static_cast<std::uint32_t>(static_cast<std::uint8_t>(bytes[2])) << 8) | static_cast<std::uint8_t>(bytes[3]);
};
auto send = [&](const std::string& data) { ::send(c, data.data(), data.size(), MSG_NOSIGNAL); };
const std::uint32_t startup = length(read(4));
read(startup - 4);
send(Msg('R', Be32(3)));
read(1);
std::string got = read(length(read(4)) - 4);
while (!got.empty() && got.back() == '\0') got.pop_back();
log.emplace_back("password", got);
if (got != password) {
send(Err("28P01", "password authentication failed"));
close(c);
close(listener);
return;
}
send(Msg('S', std::string("server_version\x00" "17.6\x00", 20)) + Msg('R', Be32(0)) + Msg('Z', "I"));
std::string status = "I";
std::string pending;
while (true) {
const std::string type = read(1);
if (type.empty()) break;
const std::string body = read(length(read(4)) - 4);
if (type == "Q") {
std::string q = body;
while (!q.empty() && q.back() == '\0') q.pop_back();
log.emplace_back("Q", q);
status = q.starts_with("BEGIN") ? "T" : (q == "ROLLBACK" ? "I" : status);
send(Msg('C', Dyn::Split(q)[0] + std::string(1, '\0')) + Msg('Z', status));
} else if (type == "P") {
pending = body.substr(1, body.find('\0', 1) - 1);
log.emplace_back("P", pending);
} else if (type == "S") {
const std::string low = Dyn::Lower(pending);
std::string out;
if (status == "E") {
out = Err("25P02", "current transaction is aborted");
} else if (pending.contains(';')) {
out = Err("42601", "cannot insert multiple commands into a prepared statement");
status = "E";
} else if (low.starts_with("commit")) {
out = Msg('C', std::string("COMMIT\0", 7));
status = "I";
} else if (low.starts_with("select")) {
out = Msg('T', std::string("\x00\x01x\x00", 4) + std::string(18, '\0')) + Msg('D', std::string("\x00\x01\x00\x00\x00\x01" "1", 7))
+ Msg('N', std::string("SNOTICE\0Mhello\0\0", 16)) + Msg('C', std::string("SELECT 1\0", 9));
} else if (low.starts_with("boom")) {
out = Err("42601", "syntax error at or near \"boom\"");
status = "E";
} else {
out = Msg('C', Dyn::Upper(Dyn::Split(pending)[0]) + std::string(1, '\0'));
}
send(Msg('1') + Msg('2') + out + Msg('Z', status));
} else if (type == "X") {
log.emplace_back("X", "");
break;
}
}
close(c);
close(listener);
}
};
}

View file

@ -2,8 +2,8 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// The SQL helper: its statements, refusals, the URL, SCRAM, the protocol on a
// stand-in server (FakePg, below). Then what the helper prints over the same
// stand-in, line for line against the goldens (fixture/pipeline.json,
// stand-in server (FakePg, tests/Common). Then what the helper prints over
// the same stand-in, line for line against the goldens (fixture/pipeline.json,
// `runs`), and the broker: the URL stays in the driver, a pass gets an
// address and a token.
@ -16,6 +16,9 @@ import Crafter.Review;
namespace fs = std::filesystem;
using namespace Crafter::Review;
#include "../Common/FakePg.hpp"
using namespace Testing;
namespace {
std::int32_t Failures = 0;
@ -24,134 +27,6 @@ namespace {
if (!cond) ++Failures;
}
std::string Be32(std::uint32_t v) {
return {static_cast<char>(v >> 24), static_cast<char>((v >> 16) & 0xFF), static_cast<char>((v >> 8) & 0xFF), static_cast<char>(v & 0xFF)};
}
std::string Msg(char type, const std::string& body = {}) {
return std::format("{}{}{}", type, Be32(static_cast<std::uint32_t>(body.size() + 4)), body);
}
std::string Err(const std::string& code, const std::string& text) {
return Msg('E', std::format("SERROR{}C{}{}M{}{}{}", std::string(1, '\0'), code, std::string(1, '\0'), text, std::string(1, '\0'), std::string(1, '\0')));
}
// A Postgres stand-in on 127.0.0.1 for one connection: cleartext
// password, simple Query, Parse/Bind/Describe/Execute/Sync. It records
// what it is sent and answers as the server does where the helper relies
// on it: two commands in one Parse are an error; COMMIT ends the
// transaction (status I); a statement after an error fails.
class FakePg {
public:
explicit FakePg(std::string password = "pw") : password(std::move(password)) {
listener = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
bind(listener, reinterpret_cast<sockaddr*>(&address), sizeof(address));
listen(listener, 1);
socklen_t length = sizeof(address);
getsockname(listener, reinterpret_cast<sockaddr*>(&address), &length);
port = ntohs(address.sin_port);
thread = std::thread([this] { Serve(); });
}
~FakePg() {
Served();
}
// The log once the connection is over.
std::vector<std::pair<std::string, std::string>> Served() {
if (thread.joinable()) thread.join();
return log;
}
std::uint16_t port = 0;
private:
std::string password;
std::int32_t listener = -1;
std::thread thread;
std::vector<std::pair<std::string, std::string>> log;
void Serve() {
const std::int32_t c = accept(listener, nullptr, nullptr);
std::string buffer;
auto read = [&](std::size_t n) {
while (buffer.size() < n) {
std::array<char, 4096> chunk {};
const ssize_t got = recv(c, chunk.data(), chunk.size(), 0);
if (got <= 0) break;
buffer.append(chunk.data(), static_cast<std::size_t>(got));
}
const std::string out = buffer.substr(0, std::min(n, buffer.size()));
buffer.erase(0, out.size());
return out;
};
auto length = [](const std::string& bytes) {
return (static_cast<std::uint32_t>(static_cast<std::uint8_t>(bytes[0])) << 24) | (static_cast<std::uint32_t>(static_cast<std::uint8_t>(bytes[1])) << 16)
| (static_cast<std::uint32_t>(static_cast<std::uint8_t>(bytes[2])) << 8) | static_cast<std::uint8_t>(bytes[3]);
};
auto send = [&](const std::string& data) { ::send(c, data.data(), data.size(), MSG_NOSIGNAL); };
const std::uint32_t startup = length(read(4));
read(startup - 4);
send(Msg('R', Be32(3)));
read(1);
std::string got = read(length(read(4)) - 4);
while (!got.empty() && got.back() == '\0') got.pop_back();
log.emplace_back("password", got);
if (got != password) {
send(Err("28P01", "password authentication failed"));
close(c);
close(listener);
return;
}
send(Msg('S', std::string("server_version\x00" "17.6\x00", 20)) + Msg('R', Be32(0)) + Msg('Z', "I"));
std::string status = "I";
std::string pending;
while (true) {
const std::string type = read(1);
if (type.empty()) break;
const std::string body = read(length(read(4)) - 4);
if (type == "Q") {
std::string q = body;
while (!q.empty() && q.back() == '\0') q.pop_back();
log.emplace_back("Q", q);
status = q.starts_with("BEGIN") ? "T" : (q == "ROLLBACK" ? "I" : status);
send(Msg('C', Dyn::Split(q)[0] + std::string(1, '\0')) + Msg('Z', status));
} else if (type == "P") {
pending = body.substr(1, body.find('\0', 1) - 1);
log.emplace_back("P", pending);
} else if (type == "S") {
const std::string low = Dyn::Lower(pending);
std::string out;
if (status == "E") {
out = Err("25P02", "current transaction is aborted");
} else if (pending.contains(';')) {
out = Err("42601", "cannot insert multiple commands into a prepared statement");
status = "E";
} else if (low.starts_with("commit")) {
out = Msg('C', std::string("COMMIT\0", 7));
status = "I";
} else if (low.starts_with("select")) {
out = Msg('T', std::string("\x00\x01x\x00", 4) + std::string(18, '\0')) + Msg('D', std::string("\x00\x01\x00\x00\x00\x01" "1", 7))
+ Msg('N', std::string("SNOTICE\0Mhello\0\0", 16)) + Msg('C', std::string("SELECT 1\0", 9));
} else if (low.starts_with("boom")) {
out = Err("42601", "syntax error at or near \"boom\"");
status = "E";
} else {
out = Msg('C', Dyn::Upper(Dyn::Split(pending)[0]) + std::string(1, '\0'));
}
send(Msg('1') + Msg('2') + out + Msg('Z', status));
} else if (type == "X") {
log.emplace_back("X", "");
break;
}
}
close(c);
close(listener);
}
};
std::uint16_t DeadPort() {
const std::int32_t s = socket(AF_INET, SOCK_STREAM, 0);
@ -185,20 +60,7 @@ namespace {
// A raw call of the broker: one line in, every line back.
std::vector<std::string> RawCall(std::uint16_t port, const std::string& line) {
const std::int32_t s = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
address.sin_port = htons(port);
connect(s, reinterpret_cast<sockaddr*>(&address), sizeof(address));
::send(s, line.data(), line.size(), MSG_NOSIGNAL);
shutdown(s, SHUT_WR);
std::string all;
std::array<char, 4096> chunk {};
ssize_t got = 0;
while ((got = recv(s, chunk.data(), chunk.size(), 0)) > 0) all.append(chunk.data(), static_cast<std::size_t>(got));
close(s);
return Dyn::Split(Dyn::RStrip(all, "\n"), "\n");
return Dyn::Split(Dyn::RStrip(BrokerCall(port, line), "\n"), "\n");
}
}

387
tests/ReviewFixes/main.cpp Normal file
View file

@ -0,0 +1,387 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// The fixes of the 2026-10-22 code review: the broker, the splitter, the
// connection, the preflight, the calibration, the sampler, the resume, the
// template. The SQL helper is this binary run as `sql`; app_ready, the
// database calls and the methods of Run are replaced through the Run's
// seams.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
import std;
import Crafter.Review;
namespace fs = std::filesystem;
using namespace Crafter::Review;
#include "../Common/FakePg.hpp"
using namespace Testing;
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 Show(const std::vector<std::string>& lines) {
Json out = Json::array();
for (const std::string& line : lines) out.push_back(line);
return Dyn::ReprValue(out);
}
// A server on 127.0.0.1 that resets every connection it takes (SO_LINGER
// 0, then close) until it goes. A real reset races the startup message,
// so either it does not go out or its answer never comes.
class Resetter {
public:
Resetter() {
listener = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
bind(listener, reinterpret_cast<sockaddr*>(&address), sizeof(address));
listen(listener, 8);
socklen_t length = sizeof(address);
getsockname(listener, reinterpret_cast<sockaddr*>(&address), &length);
port = ntohs(address.sin_port);
thread = std::thread([this] {
while (true) {
const std::int32_t c = accept(listener, nullptr, nullptr);
if (c < 0) break;
const linger hard {1, 0};
setsockopt(c, SOL_SOCKET, SO_LINGER, &hard, sizeof(hard));
close(c);
}
});
}
~Resetter() {
// A shut listening socket ends the accept that waits on it.
shutdown(listener, SHUT_RDWR);
thread.join();
close(listener);
}
std::uint16_t port = 0;
private:
std::int32_t listener = -1;
std::thread thread;
};
bool Reset(std::string_view text) {
return text.contains("Broken pipe") || text.contains("Connection reset");
}
std::int32_t Checks() {
std::println("the 2026-10-22 code review: the broker, the splitter, the connection, the preflight, the calibration, the sampler, the resume, the template");
Platform::SetMode(Platform::Mode::Native);
SetHome(fs::current_path());
const fs::path base = fs::temp_directory_path() / std::format("crafter-review-fixes-{}", Platform::PublishedPid());
fs::remove_all(base);
// finding 8: a SQL-standard routine body stays whole; ROLLBACK [WORK |
// TRANSACTION] TO goes through.
const std::string atomic = "create function f(x int) returns int language sql begin atomic select case when x > 0 then 1 else 0 end; select 2; end; "
"create or replace procedure p() language sql begin atomic insert into t values (1); end; select 3; "
"create function g() returns int language plpgsql as $$ begin return 1; end $$; select (case when true then 1 end); begin";
const std::vector<std::string> parts = PgWire::SplitStatements(atomic);
Check("split: a SQL-standard routine body (BEGIN ATOMIC ... END, a CASE ... END inside) stays one statement, CREATE OR REPLACE PROCEDURE too; "
"a plpgsql $$ body, a CASE outside a routine and a bare BEGIN cut as before (finding 8)",
parts == std::vector<std::string>{"create function f(x int) returns int language sql begin atomic select case when x > 0 then 1 else 0 end; select 2; end",
"create or replace procedure p() language sql begin atomic insert into t values (1); end", "select 3",
"create function g() returns int language plpgsql as $$ begin return 1; end $$", "select (case when true then 1 end)", "begin"},
Show(parts));
Check("split: the words are counted per statement - a BEGIN after a CREATE TABLE is no routine body; an E-string after a word is still an E-string",
PgWire::SplitStatements(R"(create table t (a int); begin; select E'it\'s; here'; select 1)")
== std::vector<std::string>{"create table t (a int)", "begin", R"(select E'it\'s; here')", "select 1"});
const std::vector<std::string> okRb = {"rollback to s", "rollback to savepoint s", "rollback work to s", "ROLLBACK TRANSACTION TO SAVEPOINT s"};
const std::vector<std::string> noRb = {"rollback", "rollback work", "rollback transaction", "rollback and chain", "rollback prepared 'x'", "rollback work and no chain"};
std::vector<std::string> verdicts;
for (const auto& list : {okRb, noRb}) {
for (const std::string& s : list) verdicts.push_back(std::format("{}: {}", s, PgWire::Refused(s).value_or("None")));
}
Check("refused: ROLLBACK [WORK | TRANSACTION] TO [SAVEPOINT] goes through and every other ROLLBACK is refused; the BEGIN ATOMIC routine is not refused",
std::ranges::all_of(okRb, [](const std::string& s) { return !PgWire::Refused(s); }) && std::ranges::all_of(noRb, [](const std::string& s) { return PgWire::Refused(s).has_value(); })
&& !PgWire::Refused(parts.at(0)),
Show(verdicts));
// finding 7: nothing but a DbError leaves a connection, whatever
// breaks during the startup message.
{
const Resetter rst;
const std::string url0 = std::format("postgresql://u:p@127.0.0.1:{}/d", rst.port);
std::optional<std::string> raised;
try {
PgWire::Connection connection(url0);
} catch (const PgWire::DbError& error) {
raised = error.what();
}
std::vector<std::string> out;
const std::int32_t code = PgWire::RunSql(url0, "select 1", [&](const std::string& line) { out.push_back(line); },
[](std::string_view u) { return PgWire::Connect(u, 1, [](double) {}); });
const auto probe0 = PgWire::Probe(url0);
Check("connection: a reset while the startup message goes out is a DbError, never an OSError - the helper says NO DATABASE (exit 2), the probe that it does not answer (finding 7)",
raised && Reset(*raised) && code == PgWire::ExitNoDb && !out.empty() && out[0].starts_with(PgWire::NoDatabase) && !probe0.first,
std::format("{} | {} | {}", raised.value_or("None"), Show(out), probe0.second));
}
{
const Resetter srv;
std::optional<std::string> reset;
try {
PgWire::Connect(std::format("postgresql://u:p@127.0.0.1:{}/d", srv.port), 1, [](double) {});
} catch (const PgWire::DbError& error) {
reset = error.what();
}
Check("connection: a server that resets the connection at once is a DbError too", reset.has_value(), reset.value_or("None"));
}
// finding 6: the broker - the URL stays in the driver, the helper
// reaches the database through it.
{
FakePg pg;
const std::string url = std::format("postgresql://u:pw@127.0.0.1:{}/db", pg.port);
PgWire::Broker br;
br.Start();
const std::string addr = br.Address(url);
std::map<std::string, std::string> envb = Platform::Environment();
envb.erase(std::string(PgWire::BrokerEnv));
envb.erase(std::string(PgWire::UrlEnv));
auto sh = [&](std::vector<std::string> args, const std::optional<std::string>& broker, std::string_view stdinText = {}) {
args.insert(args.begin(), {Platform::SelfExe().string(), "sql"});
std::map<std::string, std::string> env = envb;
if (broker) env[std::string(PgWire::BrokerEnv)] = *broker;
return Process::Run(args, std::nullopt, std::chrono::seconds(60), stdinText, env);
};
const Process::Captured r1 = sh({}, addr, "select 1;\n");
const std::vector<std::pair<std::string, std::string>> served = pg.Served();
const Process::Captured r2 = sh({"select 1"}, std::format("{}#{}", addr.substr(0, addr.rfind('#')), std::string(32, '0')));
const Process::Captured r3 = sh({"select 1"}, std::nullopt);
const Process::Captured r4 = sh({}, addr, "commit;\n");
br.Close();
const Process::Captured r5 = sh({"select 1"}, addr);
auto has = [&](const std::pair<std::string, std::string>& entry) { return std::ranges::find(served, entry) != served.end(); };
Check("broker: the helper's SQL runs through the driver - connected, the rows, rolled back, exit 0 - with only the broker's address and a token in the pass's environment (finding 6)",
r1.returnCode == 0 && r1.out.contains(PgWire::Answered) && r1.out.contains("\n x\n") && r1.out.contains("-- rolled back") && !addr.contains("pw") && has({"password", "pw"})
&& has({"Q", "ROLLBACK"}),
r1.out + r1.err);
Check("broker: a foreign token, no broker variable and a broker that is gone are NO DATABASE (exit 2); a refused statement is refused through it as before (exit 1, nothing connects)",
r2.returnCode == 2 && r2.out.contains("no database by this token") && r3.returnCode == 2 && r3.out.contains("this pass has no database") && r4.returnCode == 1
&& r4.out.contains("refused, nothing ran") && r5.returnCode == 2 && r5.out.contains("does not answer"),
Show({r2.out, r3.out, r4.out, r5.out}));
PgWire::Broker bp({}, [&](const std::string& u) { return std::pair<bool, std::string>(u == url, "db on 127.0.0.1 as u"); });
bp.Start();
const std::string probeAddr = bp.Address(url);
const Process::Captured r6 = sh({"--probe"}, probeAddr);
const std::size_t colon = probeAddr.rfind(':');
const auto port = static_cast<std::uint16_t>(std::stoi(probeAddr.substr(colon + 1, probeAddr.rfind('#') - colon - 1)));
// No newline: the call was cut off.
const std::string cut = BrokerCall(port, R"({"token": "x", "sql": "select 1"})");
bp.Close();
Check("broker: --probe goes through it too; a call that does not arrive whole runs nothing (exit 64)",
r6.returnCode == 0 && r6.out.starts_with(PgWire::Answered) && cut.contains("did not arrive whole") && Dyn::RStrip(cut).ends_with(std::format("{}{}", PgWire::ExitMark, PgWire::ExitUsage)),
Dyn::ReprValue(Json::array({r6.out, cut})));
}
// finding 4: the calibration is a record, never a dependency.
{
const fs::path td = base / "calibration";
const fs::path blocker = td / "file";
Write(blocker, "x");
// Its directory is a file.
const bool wrote = Memory::Calibration(blocker / "memory.json", td, {{"none", 210}}).Record("none", 500.0);
const fs::path mp = td / "memory.json";
Memory::Calibration cal(mp, td, {{"none", 210}, {"browser", 840}});
const std::string me = fs::canonical(td).string();
Json listed = Json::object();
listed[me] = Json::array({5, 6});
Json mixed = Json::object();
mixed[me] = {{"browser", "x"}, {"none", Json::array({true, -3, "a", 300})}};
Json shapes = Json::array();
for (const std::string& content : {std::string("[1, 2]"), DumpSpaced(listed), DumpSpaced(mixed), std::string("{not json"), std::string("\xff\xfe\x00", 3)}) {
Write(mp, content);
shapes.push_back(Json::array({cal.CostOf("browser").megabytes, cal.CostOf("none").megabytes}));
}
const bool repaired = cal.Record("browser", 700.0);
Check("calibration: a peak that cannot be written is dropped, never raised - a finished pass is never thrown away for bookkeeping (finding 4)", !wrote);
Check("calibration: a damaged file, or a part of the wrong shape, reads as absent - the measured defaults, the sound peaks kept - and the next record repairs it",
shapes == Json::parse("[[840, 210], [840, 210], [840, 300], [840, 210], [840, 210]]") && repaired && cal.CostOf("browser").megabytes == 700.0, Dyn::ReprValue(shapes));
}
// finding 11: the sampler - one process scan a sweep, a pause that
// grows with the sweep.
{
const std::map<std::int64_t, std::vector<std::int64_t>> kids = {{1, {2, 3}}, {2, {4}}, {9, {10}}};
std::vector<std::int64_t> tree = Platform::Tree(1, kids);
std::ranges::sort(tree);
const auto ppids = Platform::PpidMap();
const auto mine = ppids.find(getppid());
Check("sampler: a tree from one ppid map (no per-thread children files); the pause is the interval, or three times a sweep that took longer (finding 11)",
tree == std::vector<std::int64_t>{1, 2, 3, 4} && Platform::Tree(9, kids) == std::vector<std::int64_t>{9, 10} && Memory::Sampler::Pause(3.0, 0.2) == 3.0
&& Memory::Sampler::Pause(3.0, 2.5) == 7.5 && mine != ppids.end() && std::ranges::find(mine->second, getpid()) != mine->second.end());
std::atomic<std::int32_t> scans = 0;
Memory::Sampler sm(3600, [](std::int64_t, const std::map<std::int64_t, std::vector<std::int64_t>>&) { return 1.0; }, [&] {
++scans;
return std::map<std::int64_t, std::vector<std::int64_t>>();
});
for (std::int64_t i = 0; i < 5; ++i) sm.Watch(std::to_string(i), i);
const std::int32_t launched = scans;
sm.Sample();
Check("sampler: one scan per sweep whatever the number of passes, and none on a launch", launched == 0 && scans == 1 && sm.Done("3") == 1.0);
}
// findings 1, 5, 9, 12: the preflight.
{
const fs::path td = base / "preflight";
Run::Run g;
std::vector<std::string> logs;
g.repo = td;
g.log = [&](const std::string& line) { logs.push_back(line); };
g.cfg = {{"functional", {{"start", "make run"}, {"readyPath", "/"}, {"exclude", Json::array()}}},
{"supabase", {{"dbUrl", "postgresql://postgres:postgres@127.0.0.1:54322/postgres"}, {"start", nullptr}}},
{"slots", {{"dir", ".local-stack/slots"}, {"start", "up --slot {n}"}, {"stop", nullptr}, {"memMb", 1500}}}};
g.a.dryRun = false;
g.a.harness = "claude";
g.a.base = "";
g.slotProblems.clear();
g.loginFixture = std::nullopt;
g.trees = {td.string()};
Write(td / "l" / "u.json", R"({"email": "a@x.test", "password": "p"})");
auto slot = [](std::int32_t n, bool db = true, std::string_view user = "postgres", bool isSlot = true) {
return Json {{"id", std::format("slot {}", n)}, {"appUrl", std::format("http://localhost:2{}180", n)}, {"apiUrls", Json::array()}, {"login", "l/u.json"}, {"slot", isSlot},
{"dbUrl", db ? Json(std::format("postgresql://{}:postgres@127.0.0.1:2{}122/postgres", user, n)) : Json(nullptr)}};
};
auto pre = [&](const std::vector<std::string>& lenses, const Json& envs, const std::vector<Json>& stacks, bool slotMode = true) -> std::pair<std::string, std::string> {
g.environments = envs;
g.stacks = std::make_unique<Pool::Stacks>(stacks);
g.slotMode = slotMode;
try {
g.StackPreflight(lenses);
return {"ok", logs.empty() ? std::string() : logs.back()};
} catch (const Run::Preflight& e) {
return {"3", std::format("preflight: {}\n", e.what())};
}
};
std::vector<std::pair<std::string, std::string>> queried;
std::int32_t migCalls = 0;
const Run::QueryResult::second_type users = {{"x"}};
g.appReady = [](std::string_view u, std::string_view) { return std::pair<bool, std::string>(true, std::format("HTTP 200 from {}", u)); };
g.probe = [](std::string_view) { return std::pair<bool, std::string>(true, "postgres on 127.0.0.1 - PostgreSQL 17.6"); };
g.query = [&](std::string_view u, std::string_view sql) {
queried.emplace_back(u, sql);
return Run::QueryResult({"?"}, users);
};
g.bypassesRls = [](std::string_view u) { return std::optional<bool>(!u.contains("admin")); };
g.migrationsAtHead = [&] {
++migCalls;
return Run::MigrationsHead();
};
const Json bs = {{Fn, "browser+supabase"}, {"repo-bug-prover.md", "supabase"}};
const Json browser = {{Fn, "browser"}};
const auto nodbApp = pre({Fn}, bs, {slot(1), slot(2, false)});
const auto nodbFirst = pre({"repo-bug-prover.md"}, bs, {slot(1, false), slot(2)});
const auto nodbBrowser = pre({Fn}, browser, {slot(1), slot(2, false)});
queried.clear();
const auto implicit = pre({Fn}, browser, {slot(1, true, "postgres", false)}, false);
const auto implicitQ = queried;
const auto implicitDb = pre({Fn}, bs, {slot(1, true, "postgres", false)}, false);
const bool implicitDbQ = std::ranges::any_of(queried, [](const auto& q) { return q.second.contains("auth.users"); });
const auto mixed = pre({Fn}, bs, {slot(1), slot(2, true, "admin")});
const auto mixedRole = g.dbRole;
const auto mixedBypass = g.dbBypass;
g.migCache.clear();
migCalls = 0;
const auto three = pre({Fn}, bs, {slot(1), slot(2), slot(3)});
const auto threeRole = g.dbRole;
const auto threeChecked = g.dbChecked;
const std::int32_t threeMigs = migCalls;
const auto one = pre({"repo-bug-prover.md"}, bs, {slot(1), slot(2)});
const auto oneChecked = g.dbChecked;
Check("preflight: a stack without a database is refused for a lens that would run SQL there - any slot for browser+supabase, the first for supabase alone - "
"and is fine for a lens without the database (finding 1)",
nodbApp.first == "3" && nodbApp.second.contains("no database for slot 2 (no dbUrl") && nodbFirst.first == "3" && nodbFirst.second.contains("no database for slot 1 (no dbUrl")
&& nodbBrowser.first == "ok",
Show({nodbApp.second, nodbFirst.second, nodbBrowser.second}));
Check("preflight: the config's one stack, with a browser lens alone, checks no login in a database nobody named (the default URL may be another project's); "
"with a lens that uses its database it does (finding 5)",
implicit.first == "ok" && std::ranges::none_of(implicitQ, [](const auto& q) { return q.second.contains("auth.users"); }) && implicitDb.first == "ok" && implicitDbQ,
Show({implicit.second, implicitDb.second}));
Check("preflight: the role the block names is the stacks' own, none when they differ; 'each of the 3 stacks' only when 3 were probed, 'it answers' for the one "
"a lens with the database alone uses (finding 9)",
mixed.first == "ok" && !mixedRole && !mixedBypass && three.first == "ok" && threeRole == "postgres" && threeChecked
&& threeChecked->starts_with("the database of each of the 3 stacks answers") && one.first == "ok" && oneChecked && oneChecked->starts_with("it answers ("),
Show({mixedRole.value_or("None"), threeChecked.value_or("None"), oneChecked.value_or("None")}));
Check("preflight: the HEAD side of the migrations check is worked out once for all the stacks (finding 12)", threeMigs == 1, std::to_string(threeMigs));
}
// finding 10: a gate whose every lens resumes needs no stack.
{
Run::Run g;
std::vector<std::vector<std::string>> calls;
std::vector<std::string> logs;
g.log = [&](const std::string& line) { logs.push_back(line); };
g.resumable = [](const std::string& name, const std::string&, std::string_view) { return std::optional<Json>(Json {{"lens", name}, {"verdict", "SEND-READY"}}); };
g.instrumentPreflight = [&](std::span<const std::string> lfs) { calls.emplace_back(lfs.begin(), lfs.end()); };
const std::vector<Json> res = g.RunGate("Gate 2 — Functional", {Fn});
const auto resumedCalls = calls;
g.resumable = [](const std::string&, const std::string&, std::string_view) { return std::optional<Json>(); };
g.instrumentPreflight = [&](std::span<const std::string> lfs) {
calls.emplace_back(lfs.begin(), lfs.end());
throw std::runtime_error("stop here");
};
try {
g.RunGate("Gate 2 — Functional", {Fn});
} catch (const std::runtime_error&) {
}
Check("run_gate: a gate whose every lens resumes from the checkpoint runs no preflight - nothing will touch the stack; a lens that runs gets it (finding 10)",
resumedCalls.empty() && res.size() == 1 && Dyn::Equal(res[0], {{"lens", "repo-functional"}, {"verdict", "SEND-READY"}}) && calls == std::vector<std::vector<std::string>>{{Fn}},
std::format("{} resumed call(s), {} call(s)", resumedCalls.size(), calls.size()));
}
// finding 13: the block states :PgWire's own limits. They are
// constexpr and the block formats them, so the check pins the text it
// formats and today's values.
{
const std::string b1 = Prompt::DbBlock(Json::object(), "c", std::string("postgres"));
const std::string limits =
std::format("statement_timeout {} s and lock_timeout {} s per statement; at most {} rows", PgWire::StatementTimeoutS, PgWire::LockTimeoutS, PgWire::MaxRows);
const std::string differs = Prompt::DbBlock(Json::object(), "c", std::nullopt);
Check("db block: the limits are pgwire's own constants, so the prompt and the helper cannot drift apart (finding 13); a role that differs between stacks is not named",
b1.contains("statement_timeout 20 s and lock_timeout 3 s per statement; at most 100 rows") && b1.contains(limits) && differs.contains("differs between the stacks")
&& !differs.contains("`None`"));
}
// finding 2: the template loads the way a consumer meets it.
{
const fs::path td = base / "template";
fs::create_directories(td / ".review-gates");
fs::copy_file(fs::current_path() / "process" / "templates" / "config.json", td / ".review-gates" / "config.json");
const Config::Loaded loaded = Config::Load(td);
Check("template: process/templates/config.json, copied as the README says, loads with no problem and no warning - unknown keys included (finding 2)",
loaded.problems.empty() && loaded.config.at("_warnings").empty(), Show(loaded.problems));
}
fs::remove_all(base);
return Failures ? 1 : 0;
}
}
int main(int argc, char** argv) {
const std::vector<std::string_view> args(argv, argv + argc);
// The helper a lens runs, for the broker checks.
if (args.size() > 1 && args[1] == "sql") return PgWire::SqlMain(std::span(args).subspan(2));
try {
return Checks();
} catch (const std::exception& error) {
std::println(std::cerr, "review fixes: {}", error.what());
return 1;
}
}

348
tests/StacksMemory/main.cpp Normal file
View file

@ -0,0 +1,348 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Stacks and memory: the slots config, slot.json, free memory, a pass's
// measured cost, the admission, the prompts, the preflight, `slots`.
// app_ready, the database calls, the memory readings and the methods of Run
// are replaced through the Run's seams.
#include <stdlib.h>
#include <unistd.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;
}
void Write(const fs::path& path, std::string_view text) {
fs::create_directories(path.parent_path());
std::ofstream(path, std::ios::binary) << text;
}
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); });
}
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("stacks and memory: the slots config, slot.json, free memory, a pass's measured cost, the admission, the prompts, the preflight, `slots`");
Platform::SetMode(Platform::Mode::Native);
SetHome(fs::current_path());
const fs::path base = fs::temp_directory_path() / std::format("crafter-review-stacks-{}", Platform::PublishedPid());
fs::remove_all(base);
{
const fs::path td = base / "one";
fs::create_directories(td / ".review-gates");
const fs::path p = td / ".review-gates" / "config.json";
auto bad = [&](const Json& d) {
Write(p, DumpSpaced(d));
return Config::Load(td).problems;
};
auto sl = [](const Json& kw) { return Json {{"slots", kw}}; };
Check("config: slots default to none; a dir inside the repository, start/stop with {n}, and the measured memMb with it; anything else is a problem",
Dyn::Equal(Config::Defaults().at("slots"), {{"dir", nullptr}, {"start", nullptr}, {"stop", nullptr}, {"memMb", nullptr}, {"startMb", nullptr}})
&& AnyContains(bad(sl({{"dir", "s"}, {"memMb", 1}, {"startMb", 0}})), "slots.startMb")
&& bad(sl({{"dir", ".local-stack/slots"}, {"start", "bash s.sh full --slot {n}"}, {"stop", "bash s.sh stop --slot {n}"}, {"memMb", 1500}})).empty()
&& AnyContains(bad(sl({{"dir", "/abs"}, {"memMb", 1}})), "inside the repository") && AnyContains(bad(sl({{"dir", "s"}, {"start", "bash s.sh"}, {"memMb", 1}})), "{n}")
&& AnyContains(bad(sl({{"dir", "s"}})), "slots.memMb") && AnyContains(bad(sl({{"dir", "s"}, {"memMb", true}})), "slots.memMb") && bad({{"slots", {{"nope", 1}}}}).size() == 1);
Write(p, R"({"functional": {"concurrency": 4}})");
const Config::Loaded cr = Config::Load(td);
const Config::Loaded cr2 = Config::Load("/", p);
const Json& warnings = cr.config.at("_warnings");
const std::string w0 = warnings.empty() ? std::string() : warnings.at(0).get<std::string>();
Check("config: functional.concurrency is retired - no problem (a freeze or a run goes on), the key dropped, a warning that says what replaced it; the --config path kept; no _warnings in the dump",
cr.problems.empty() && cr2.problems.empty() && warnings.size() == 1 && w0.starts_with("functional.concurrency is no longer read - remove it from config.json:") && w0.contains("free memory")
&& w0.contains("slots") && !cr.config.at("functional").contains("concurrency") && cr2.config.at("_path") == p.string() && !Config::Redacted(cr.config).contains("_warnings")
&& Config::Load(td / "nowhere").config.at("_warnings").empty(),
w0);
const fs::path sd = td / ".local-stack" / "slots";
const std::vector<std::pair<std::string, Json>> slots = {
{"10", {{"appUrl", "http://localhost:21080"}, {"apiUrls", {"http://127.0.0.1:21021"}}, {"dbUrl", "postgresql://postgres:postgres@127.0.0.1:21022/postgres"}, {"login", ".local-stack/slots/10/u.json"}}},
{"2", {{"appUrl", "http://localhost:20280/"}, {"apiUrls", Json::array()}, {"dbUrl", nullptr}, {"login", nullptr}}},
{"1", {{"appUrl", "http://localhost:20180"}, {"dbUrl", "postgresql://u:p@db.example.supabase.co:5432/postgres"}}},
{"3", {{"apiUrls", Json::array()}}},
{"4", nullptr},
};
for (const auto& [n, body] : slots) {
fs::create_directories(sd / n);
if (!body.is_null()) Write(sd / n / "slot.json", DumpSpaced(body));
}
fs::create_directories(sd / "5");
Write(sd / "5" / "slot.json", "{bad");
Write(sd / "10" / "u.json", "{}");
const Json cfg = {{"slots", {{"dir", ".local-stack/slots"}, {"start", "up --slot {n}"}, {"stop", "down --slot {n}"}, {"memMb", 1500}}}};
const Config::Slots found = Config::SlotStacks(cfg, td);
std::vector<std::string> ids;
for (const Json& s : found.stacks) ids.push_back(s.at("id").get<std::string>());
Check("slot_stacks: the slots that are up in slot order (2, 10), the URL without its trailing slash; a hosted database, no appUrl and bad JSON are problems; a dir without slot.json is not up",
ids == std::vector<std::string>{"slot 2", "slot 10"} && found.stacks.at(0).at("appUrl") == "http://localhost:20280" && found.stacks.at(1).at("login") == ".local-stack/slots/10/u.json"
&& found.stacks.at(1).at("dbUrl").get<std::string>().ends_with(":21022/postgres") && std::ranges::all_of(found.stacks, [](const Json& s) { return s.at("slot") == true; })
&& found.problems.size() == 3 && AnyContains(found.problems, "slots/1/slot.json: dbUrl") && AnyContains(found.problems, "slots/3/slot.json: no appUrl")
&& AnyContains(found.problems, "slots/5/slot.json: not JSON"),
Dyn::Join(found.problems, " | "));
const std::vector<std::int64_t> threeFour = {3, 4};
const std::vector<std::int64_t> justOne = {1};
Check("slot_commands: the start and stop commands for the slots named; none without a command",
Config::SlotCommands(cfg, "start", threeFour) == std::vector<std::string>{"up --slot 3", "up --slot 4"} && Config::SlotCommands({{"slots", {{"stop", nullptr}}}}, "stop", justOne).empty());
// Memory: what is free, the reserve, a tree's PSS, the sampler, the
// calibration.
const fs::path mi = td / "meminfo";
Write(mi, "MemTotal: 131371024 kB\nMemFree: 3000000 kB\nMemAvailable: 82119216 kB\nHugePages_Total: 0\n");
Check("memory: MemAvailable and MemTotal in MB; the reserve is 10 % of MemTotal (at least 2048 MB) unless REVIEW_GATES_MEM_RESERVE_MB says otherwise; no file, no number",
Memory::Available(mi) == 80194 && Platform::MemInfo(mi).at("MemTotal") == 128292 && Memory::Reserve(std::nullopt, mi) == 12829 && Memory::Reserve(std::string("4096"), mi) == 4096
&& Memory::Reserve(std::nullopt, td / "none") == 2048 && !Memory::Available(td / "none"));
const std::int64_t me = getpid();
const std::vector<std::int64_t> tree = Platform::Tree(me, Platform::PpidMap());
const std::vector<std::int64_t> gone = {(1 << 22) + 7};
Check("memory: a process tree's PSS - this process measures above zero, a pid that is gone measures zero", Platform::PssMb(tree) > 1 && Platform::PssMb(gone) == 0 && std::ranges::contains(tree, me));
std::deque<double> vals = {100.0, 400.0, 250.0};
Memory::Sampler sm(3600, [&](std::int64_t, const auto&) {
const double v = vals.front();
vals.pop_front();
return v;
}, [] { return std::map<std::int64_t, std::vector<std::int64_t>> {}; });
sm.Watch("a", 1);
sm.Sample();
sm.Sample();
sm.Sample();
const std::optional<double> peak = sm.Done("a");
Check("Sampler: the peak of the samples of a watched pass, and nothing once it is done", peak == 400.0 && !sm.Done("a"));
Memory::Calibration cal(td / "memory.json", td, {{"none", 300}, {"browser", 900}});
const Memory::Calibration::Cost d0 = cal.CostOf("browser");
cal.Record("browser", 500.0);
cal.Record("browser", 1200.0);
for (std::size_t i = 0; i < Memory::Keep; ++i) cal.Record("browser", 700.0);
const Memory::Calibration other(td / "memory.json", td / "elsewhere", {{"none", 300}, {"browser", 900}});
Check("Calibration: the measured default before any peak; then the highest of the last 30 peaks of this repository and environment (an old outlier ages out); another repository keeps its own",
d0.megabytes == 900 && d0.source == "the default measured 2026-10-22; none measured in this repository yet" && cal.CostOf("browser").megabytes == 700.0 && cal.Peaks("browser").size() == Memory::Keep
&& other.CostOf("browser").megabytes == 900 && cal.CostOf("none").megabytes == 300);
}
// Stacks: exclusive, least recently returned first, the preferred one
// only.
{
Pool::Stacks st({{{"id", "s1"}}, {{"id", "s2"}}, {{"id", "s3"}}});
const Json a = st.Take();
const Json b = st.Take();
st.Give(a.at("id"));
const bool third = st.Take().at("id") == "s3";
const bool busy = !st.Can(std::string("s2"));
const bool back = st.Can(std::string("s1"));
const bool preferred = st.Take(std::string("s1")).at("id") == "s1";
const bool none = !st.Can();
st.Give("s2");
Check("Stacks: two takes are two stacks; a returned one goes to the back; prefer takes that one or reports it busy",
a.at("id") == "s1" && b.at("id") == "s2" && third && busy && back && preferred && none && st.Can());
}
// Admission: stacks, the memory budget, the machine's free memory now,
// nothing in flight.
{
std::vector<std::string> logs;
double free = 10'000;
Pool::Stacks st2({{{"id", "slot 1"}}});
Pool::Admission adm([](const Json& j) { return j.at("mb").get<double>(); }, [](const Json& j) { return Dyn::Truthy(Dyn::Get(j, "app")); }, &st2, 1000.0, 500,
[&]() -> std::optional<double> { return free; }, [&](const std::string& line) { logs.push_back(line); });
Json j1 = {{"label", "a"}, {"mb", 600}, {"app", true}};
const Json j2 = {{"label", "b"}, {"mb", 600}};
const Json j3 = {{"label", "c"}, {"mb", 300}, {"app", true}};
const bool first = adm.Can(j1);
adm.Take(j1);
Check("Admission: a pass with an application takes a stack of its own and its cost; the next one waits for the stack, one over the budget waits for memory",
first && j1.at("stack").at("id") == "slot 1" && j1.at("prevStack") == "slot 1" && !adm.Can(j3) && !adm.Can(j2) && adm.Held() == 600 && adm.Inflight() == 1);
const Json j4 = {{"label", "d"}, {"mb", 300}};
free = 700;
const bool low = adm.Can(j4);
free = 10'000;
Check("Admission: inside the budget but the machine has less than cost + reserve free right now -> wait; with room -> go", !low && adm.Can(j4));
adm.Give(j1);
Json big = {{"label", "e"}, {"mb", 5000}};
const bool alone = adm.Can(big);
adm.Take(big);
Check("Admission: given back, the stack and the memory are free again; with nothing in flight a pass over the budget runs alone, and the log says so once",
st2.Can() && adm.Held() == 5000 && alone && logs.size() == 1 && logs.front().contains("launched alone") && !j1.contains("stack"));
adm.Give(big);
Pool::Stacks st3({{{"id", "slot 1"}}, {{"id", "slot 2"}}});
Pool::Admission adm3([](const Json&) { return 1.0; }, [](const Json&) { return true; }, &st3);
Json taken = {{"label", "x"}};
adm3.Take(taken);
Json rj = {{"label", "r"}, {"resumeSession", "sid"}, {"prevStack", "slot 1"}};
const bool waits = !adm3.Can(rj);
adm3.Give(taken);
const bool can = adm3.Can(rj);
adm3.Take(rj);
Check("Admission: a resumed pass waits for the stack its transcript ran on, not for any", waits && can && rj.at("stack").at("id") == "slot 1");
}
// The prompts: the shared block names no stack in slot mode; the pass's
// own goes into its user message.
const Json fcfg = {{"appUrl", "http://localhost:8080"}, {"apiUrls", {"http://127.0.0.1:54321"}}, {"login", {{"notes", "TOTP enrolled"}}}};
const std::string abSlots = Prompt::AppBlock(fcfg, std::string(".local-stack/test-user.json"), std::string("/p/bin/crafter-review totp .local-stack/test-user.json"), true);
const std::string abOne = Prompt::AppBlock(fcfg, std::string(".local-stack/test-user.json"), std::string("/p/bin/crafter-review totp .local-stack/test-user.json"));
Check("app block with slots: a stack of this pass's own, STACK FOR THIS PASS named, no URL, origin, fixture or TOTP command in the shared block; the rules: yours alone, not reset between passes",
abSlots.contains("a stack of this pass's own") && abSlots.contains("STACK FOR THIS PASS") && !abSlots.contains("localhost:8080") && !abSlots.contains("54321") && !abSlots.contains("test-user.json")
&& !abSlots.contains("/p/bin/crafter-review") && abSlots.contains("TOTP enrolled") && abSlots.ends_with(Prompt::ShotRules) && abSlots.contains("not reset between passes") && abOne.contains("localhost:8080")
&& abOne.contains("test-user.json") && abOne.contains("not reset between passes") && !abOne.contains("STACK FOR THIS PASS"));
const Json stk = {{"id", "slot 2"}, {"appUrl", "http://localhost:20280"}, {"apiUrls", {"http://127.0.0.1:20221"}}, {"dbUrl", "postgresql://postgres:secret@127.0.0.1:20222/postgres"},
{"login", ".local-stack/slots/2/test-user.json"}};
const std::string stackLogin = stk.at("login").get<std::string>();
const std::string tx = Prompt::StackText(stk, Pool::TotpCommand(stackLogin));
const Json seven = {{"maxTurns", 7}};
const std::string up = Prompt::UserPrompt("TAIL", seven, tx);
const std::string up0 = Prompt::UserPrompt("TAIL", seven);
Check("user prompt: the pass's stack - its URL, origins, login and exact TOTP command, never its database URL - between the scope and the budget line; without one the message is what it was",
up.find("TAIL") < up.find("STACK FOR THIS PASS (slot 2, yours alone while this pass runs)") && up.find("STACK FOR THIS PASS (slot 2, yours alone while this pass runs)") < up.find("Tool-call budget")
&& up.contains("http://localhost:20280") && up.contains("20221") && up.contains(std::format("`{}`", *Pool::TotpCommand(stackLogin))) && !up.contains("20222") && !up.contains("secret")
&& up0 == std::format("TAIL{}\n\nReturn the structured output described in your instructions.\n", Prompt::BudgetLine(seven)) && Prompt::StackText(With(stk, {{"login", nullptr}}), std::nullopt).contains("no login fixture"));
Check("functional block hint: the stack is the pass's own and not reset between passes",
Prompt::FunctionalBlockHint.contains("yours alone while this pass runs, but it is not reset between passes") && !Prompt::FunctionalBlockHint.contains("at the same time"));
{
const fs::path td = base / "runner";
fs::create_directories(td);
PgWire::Broker broker;
broker.Start();
const Pool::Runner r({.repo = fs::current_path(), .scratch = td, .bin = "/bin/true", .effort = "xhigh", .log = [](const std::string&) {}, .dryRun = true,
.functional = {{"mcp", {{"command", "npx"}, {"args", nullptr}}}, {"extraTools", Json::array()}}, .loginFixture = ".local-stack/test-user.json",
.environments = {{Fn, "browser+supabase"}, {"repo-bug-prover.md", "supabase"}}, .dbUrl = "postgresql://postgres:postgres@127.0.0.1:54322/postgres", .broker = &broker});
const std::vector<std::string> here = {"."};
auto allowed = [](const std::vector<std::string>& c) { return c[static_cast<std::size_t>(std::ranges::find(c, "--allowedTools") - c.begin()) + 1]; };
const std::string al2 = allowed(r.Cmd({{"label", "f"}, {"model", "m"}, {"lensFile", Fn}, {"kind", "block"}, {"stack", stk}}, "/dev/null", "{}", here, 20, false, "sid"));
const std::string al1 = allowed(r.Cmd({{"label", "f"}, {"model", "m"}, {"lensFile", Fn}, {"kind", "block"}}, "/dev/null", "{}", here, 20, false, "sid"));
Check("runner: a pass on a slot may run the TOTP command of that slot's login alone; a pass without a stack keeps the run's fixture",
al2.contains(std::format("Bash({}:*)", *Pool::TotpCommand(stackLogin))) && !Dyn::Replace(al2, stackLogin, "").contains("test-user.json:*")
&& al1.contains(std::format("Bash({}:*)", *Pool::TotpCommand(".local-stack/test-user.json"))));
const std::string brokerEnv(PgWire::BrokerEnv);
auto dbOf = [&](const std::map<std::string, std::string>& e) {
const std::string v = e.at(brokerEnv);
return broker.UrlOf(v.substr(v.rfind('#') + 1)).value_or("");
};
Check("runner: the database a pass reaches is its stack's; a pass with the database alone reaches the run's first",
dbOf(r.ProcessEnv({{"lensFile", Fn}, {"stack", stk}}, {})) == stk.at("dbUrl").get<std::string>() && dbOf(r.ProcessEnv({{"lensFile", "repo-bug-prover.md"}}, {})).ends_with(":54322/postgres"));
broker.Close();
}
// The preflight: slots up, every stack answers, its login in its
// auth.users.
{
const fs::path td = base / "preflight";
fs::create_directories(td);
Run::Run g;
std::vector<std::string> logs;
g.repo = td;
g.log = [&](const std::string& line) { logs.push_back(line); };
g.cfg = {{"functional", {{"start", "make run"}, {"readyPath", "/"}, {"exclude", Json::array()}}},
{"supabase", {{"dbUrl", "postgresql://postgres:postgres@127.0.0.1:1/postgres"}, {"start", nullptr}}},
{"slots", {{"dir", ".local-stack/slots"}, {"start", "up --slot {n}"}, {"stop", nullptr}, {"memMb", 1500}}}};
g.environments = {{Fn, "browser+supabase"}, {"repo-bug-prover.md", "supabase"}};
g.a.dryRun = false;
g.a.harness = "claude";
g.trees = {"."};
auto pre = [&](const std::vector<std::string>& lenses) -> std::pair<std::string, std::string> {
try {
g.StackPreflight(lenses);
return {"ok", logs.empty() ? std::string() : logs.back()};
} catch (const Run::Preflight& e) {
return {"3", std::format("preflight: {}\n", e.what())};
}
};
g.slotMode = true;
g.stacks = std::make_unique<Pool::Stacks>(std::vector<Json> {{{"id", "slot 1 (dry run: none is up)"}, {"appUrl", "http://x"}, {"apiUrls", Json::array()}, {"dbUrl", nullptr}, {"login", nullptr}, {"slot", true}}});
const auto noneUp = pre({Fn});
g.slotProblems = {".local-stack/slots/3/slot.json: no appUrl"};
const auto broken = pre({Fn});
g.slotProblems.clear();
Write(td / "l" / "u.json", R"({"email": "A@x.test", "password": "p"})");
std::vector<Json> two;
for (const std::int32_t n : {1, 2}) {
two.push_back({{"id", std::format("slot {}", n)}, {"appUrl", std::format("http://localhost:2{}180", n)}, {"apiUrls", Json::array()},
{"dbUrl", std::format("postgresql://postgres:postgres@127.0.0.1:2{}122/postgres", n)}, {"login", "l/u.json"}, {"slot", true}});
}
g.stacks = std::make_unique<Pool::Stacks>(two);
std::vector<std::string> answered;
std::vector<std::string> probed;
Run::QueryResult::second_type users = {{"x"}};
g.appReady = [&](std::string_view url, std::string_view) {
answered.emplace_back(url);
return std::pair<bool, std::string>(true, std::format("HTTP 200 from {}", url));
};
g.probe = [&](std::string_view url) {
probed.emplace_back(url);
return std::pair<bool, std::string>(true, "postgres on 127.0.0.1 as postgres - PostgreSQL 17.6");
};
g.query = [&](std::string_view, std::string_view) { return Run::QueryResult({"?"}, users); };
g.bypassesRls = [](std::string_view) { return std::optional<bool>(true); };
g.migrationsDrift = [](const std::string&) { return std::pair<std::optional<std::string>, std::optional<std::string>>(std::nullopt, "its 2 applied migrations are the range HEAD's"); };
const auto good = pre({Fn});
const std::vector<std::string> appSeen = answered;
const std::vector<std::string> dbSeen = probed;
answered.clear();
probed.clear();
const auto onlyDb = pre({"repo-bug-prover.md"});
const std::vector<std::string> dbOnlySeen = probed;
users.clear();
const auto stale = pre({Fn});
users.push_back({"x"});
g.appReady = [](std::string_view url, std::string_view) { return std::pair<bool, std::string>(!url.contains(":22180"), std::format("connection refused by {}", url)); };
const auto down = pre({Fn});
Check("preflight: slots configured and none up stops the run and names `<invocation> slots`; a broken slot.json stops it",
noneUp.first == "3" && noneUp.second.contains("no slot is up under .local-stack/slots") && noneUp.second.contains(std::format("`{} slots`", Invocation())) && broken.first == "3"
&& broken.second.contains("slots/3/slot.json"),
noneUp.second + broken.second);
Check("preflight: a lens that drives the application checks every stack - its app, its database, its migrations; a lens with the database alone checks the first",
good.first == "ok" && appSeen.size() == 2 && dbSeen.size() == 2 && dbOnlySeen == std::vector<std::string>{two.front().at("dbUrl").get<std::string>()} && good.second.contains("2 slot(s) under .local-stack/slots")
&& g.dbChecked && g.dbChecked->starts_with("the database of each of the 2 stacks answers") && onlyDb.first == "ok",
good.second);
Check("preflight: a stack whose login is not in its auth.users stops the run (a fixture left from another bring-up); an app that does not answer names its slot and the start command",
stale.first == "3" && stale.second.contains("slot 1: its login l/u.json (a@x.test) is not in that stack's auth.users") && down.first == "3" && down.second.contains("slot 2: the application does not answer")
&& down.second.contains("(up --slot {n})"),
stale.second + down.second);
// exec_job: the stack the admission gave, the measured peak into the
// calibration.
const fs::path fx = fs::current_path() / "tests" / "Goldens" / "fixture" / "commits";
g.a.artifact = fx.string();
g.a.dryRun = false;
g.a.effort = "xhigh";
g.a.noSalvage = true;
g.hash = Artifact::ArtifactHash("commits", fx);
g.testMode = true;
g.schemas = {{"block", "{}"}, {"whole", "{}"}};
g.treeDrift = [] { return std::optional<std::string>(); };
g.promptFor = [](const Json&) { return std::pair<fs::path, std::string>("/dev/null", "PROMPT"); };
g.calibration = std::make_unique<Memory::Calibration>(td / "memory.json", td, std::map<std::string, std::int64_t> {{"none", 300}, {"browser+supabase", 900}});
g.runPass = [](const Json& job, const std::string&, const fs::path&, std::string_view, std::int64_t, std::int64_t, bool) {
return std::pair<Json, Json>({{"label", job.at("label")}, {"status", "ok"}, {"memPeakMb", 812.5}}, nullptr);
};
const auto [rec, raw] = g.ExecJob({{"label", "f1"}, {"lens", "repo-functional"}, {"lensFile", Fn}, {"kind", "block"}, {"model", "m"}, {"stack", g.stacks->All().at(1)}}, 1);
Check("exec_job: the record names the pass's stack, and its measured peak becomes its environment's calibration", rec.at("stack") == "slot 2" && g.calibration->CostOf("browser+supabase").megabytes == 812.5);
// `slots`: as many as free memory holds, no more than the range's
// browser passes, the commands for the missing ones.
Write(td / ".local-stack" / "slots" / "1" / "slot.json", R"({"appUrl": "http://localhost:20180", "apiUrls": [], "dbUrl": null, "login": null})");
Json cfg = Config::Load(td).config;
cfg["slots"] = {{"dir", ".local-stack/slots"}, {"start", "up --slot {n}"}, {"stop", nullptr}, {"memMb", 1500}, {"startMb", 6000}};
cfg["lenses"]["repo-functional"]["environment"] = "browser+supabase";
std::vector<std::string> out;
const Run::Machine machine = {.available = [] { return std::optional<std::int64_t>(20'000); }, .reserve = [] { return std::int64_t {4000}; }};
auto sink = [&](const std::string& line) { out.push_back(line); };
const auto [want, cmds] = Run::SlotsPlan(cfg, td, std::nullopt, td, sink, machine);
const auto [want2, cmds2] = Run::SlotsPlan(cfg, td, fx, td, sink, machine);
std::vector<std::string> expected;
for (std::int32_t n = 2; n < 8; ++n) expected.push_back(std::format("up --slot {}", n));
// 20000 free - 4000 reserve - 1 slot up x 812.5 = 15187.5 for (1500 +
// 812.5) each -> 6 more.
Check("slots: room = free - reserve - the passes of the slots up, each new slot at memMb plus one measured pass -> 6 more, numbered after the ones up; with the fixture range no more than its 3 browser block passes",
want == 7 && cmds == expected && want2 == 3 && cmds2 == std::vector<std::string>{"up --slot 2", "up --slot 3"} && AnyContains(out, "room for 6 more by memory"), Dyn::Join(out, " | "));
Check("slots: no more start at once than free memory holds at slots.startMb each (15187 MB / 6000 -> 2), and one at a time without it", AnyContains(out, "at most 2 at once (slots.startMb 6000 MB each"),
Dyn::Join(out, " | "));
}
fs::remove_all(base);
return Failures == 0 ? 0 : 1;
}

343
tests/StoreRules/main.cpp Normal file
View file

@ -0,0 +1,343 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Store rules: what unit-state stores as reusable, reads per round, the
// reads a unit needs, salvage by environment, the environment on the store
// side, resume by environment, the migrations preflight and the instrument
// preflight. A Run is built part by part.
#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;
}
void Write(const fs::path& path, std::string_view text) {
fs::create_directories(path.parent_path());
std::ofstream(path, std::ios::binary) << text;
}
std::set<std::string> Keys(const Json& object) {
std::set<std::string> keys;
for (auto it = object.begin(); it != object.end(); ++it) keys.insert(it.key());
return keys;
}
Json Defaults() {
Json out = Json::object();
for (const Plan::Lens& l : Plan::Lenses) out[std::string(l.file)] = std::string(l.env);
return out;
}
std::string Git(const fs::path& repo, std::vector<std::string> args) {
std::vector<std::string> argv = {"git", "-C", repo.string(), "-c", "user.email=t@t", "-c", "user.name=t"};
std::ranges::move(args, std::back_inserter(argv));
const Process::Captured run = Process::Run(argv);
if (run.returnCode != 0) throw std::runtime_error(std::format("{}: {}", Dyn::Join(argv, " "), run.err));
return Dyn::Strip(run.out);
}
}
int main() {
std::println("unit-state: what is stored as reusable; reads per round; reads needed per lens");
Platform::SetMode(Platform::Mode::Native);
// No git of this test reads a global config.
setenv("GIT_CONFIG_GLOBAL", "/dev/null", 1);
setenv("GIT_CONFIG_SYSTEM", "/dev/null", 1);
const fs::path td = fs::temp_directory_path() / std::format("crafter-review-store-rules-{}", Platform::PublishedPid());
fs::remove_all(td);
fs::create_directories(td);
auto noLog = [](const std::string&) {};
auto stored = [&](std::vector<Json> recs, const Json& parts, const Json& coverage, const Json& findings, const Json& before = Json::object(), std::int64_t rnd = 1, bool twice = false) {
Run::Run r;
r.records = std::move(recs);
r.hash = "H";
r.h8 = "H";
r.round = rnd;
r.dumpPath = td / std::format("run-{}.json", rnd);
r.repo = td;
r.results = {{{"lens", "L"}, {"coverage", coverage}, {"findings", findings}, {"notes", Json::array()}, {"parts", parts}}};
r.state = {{"version", 1}, {"entries", before}, {"personaPass", nullptr}};
r.statePath = td / "unit-state.json";
r.a.date = "2026-10-15";
r.log = noLog;
// The file is the record the store merges into (2026-10-31).
State::SaveWhole(r.statePath, r.state);
r.StoreState();
if (twice) r.StoreState();
return r.state.at("entries");
};
auto rec = [](const std::string& u, const std::string& k) {
return Json {{"label", std::format("L:block:{}", u)}, {"lens", "L"}, {"lensFile", "repo-bug-prover.md"}, {"unit", u}, {"kind", "block"}, {"status", "ok"}, {"reuseKey", k}};
};
auto part = [](const std::string& u, const std::string& v) {
return Json {{"label", std::format("L:block:{}", u)}, {"kind", "block"}, {"unit", u}, {"verdict", v}, {"findings", v != "SEND-READY" ? 1 : 0}};
};
auto cov = [](const std::string& u, const std::string& v) { return Json {{"item", u}, {"verdict", v}, {"basis", ""}}; };
auto finding = [](const std::string& item, bool discharged, const std::optional<std::string>& by = std::nullopt) {
Json f = {{"finding", "f"}, {"items", Json::array({item})}, {"discharged", discharged}};
if (by) f["dischargedBy"] = *by;
return f;
};
Json e = stored({rec("X", "kx"), rec("Y", "ky")}, Json::array({part("X", "SEND-READY"), part("Y", "SEND-READY")}), Json::array({cov("X", "clear"), cov("Y", "clear")}), Json::array());
Check("two clean passes -> both stored", Keys(e) == std::set<std::string>{"kx", "ky"} && e.at("kx").at("unit") == "X");
e = stored({rec("X", "kx"), rec("Y", "ky")}, Json::array({part("X", "FIX FIRST"), part("Y", "SEND-READY")}), Json::array({cov("X", "clear"), cov("Y", "clear")}), Json::array({finding("Y", false)}),
{{"kx", {{"unit", "X"}}}});
Check("the pass on X returns a finding that cites only Y -> neither stored, X loses its earlier entry", e.empty(), e.dump());
e = stored({rec("X", "kx"), rec("Y", "ky")}, Json::array({part("X", "SEND-READY"), part("Y", "SEND-READY")}), Json::array({cov("X", "defect"), cov("Y", "clear")}), Json::array({finding("X", true, "w")}));
Check("a defect verdict whose only citing finding is discharged -> stored", Keys(e) == std::set<std::string>{"kx", "ky"} && e.at("kx").at("coverage").at("verdict") == "defect");
e = stored({rec("X", "kx"), rec("Y", "ky")}, Json::array({part("X", "SEND-READY"), part("Y", "SEND-READY")}), Json::array({cov("X", "defect"), cov("Y", "clear")}), Json::array());
Check("a defect verdict with no citing finding -> not stored", Keys(e) == std::set<std::string>{"ky"});
e = stored({rec("p3:absent", "k"), rec("p4:absent", "k")}, Json::array({part("p3:absent", "SEND-READY"), part("p4:absent", "FIX FIRST")}), Json::array({cov("p3:absent", "clear"), cov("p4:absent", "defect")}),
Json::array({finding("p4:absent", false)}), {{"k", {{"unit", "p3:absent"}}}, {"other", {{"unit", "Z"}}}});
Check("two units under one key, one defective -> the key is not stored and its earlier entry is dropped", Dyn::Equal(e, {{"other", {{"unit", "Z"}}}}), e.dump());
e = stored({rec("p3:x#hdr", "k"), rec("p8:x#hdr", "k")}, Json::array({part("p3:x#hdr", "SEND-READY"), part("p8:x#hdr", "SEND-READY")}), Json::array({cov("p3:x#hdr", "clear"), cov("p8:x#hdr", "n/a")}), Json::array());
Check("two units under one key, both clean -> stored once", Keys(e) == std::set<std::string>{"k"});
const Json oneRead = Json::array({{{"hash", "H"}, {"round", 1}, {"run", "run-1.json"}}});
const Json e1 = stored({rec("X", "kx")}, Json::array({part("X", "SEND-READY")}), Json::array({cov("X", "clear")}), Json::array(), Json::object(), 1, true);
Check("reads: a clean pass is one read however often the run stores it",
Plan::ReadsOf(e1.at("kx")) == 1 && Dyn::Equal(e1.at("kx").at("reads"), oneRead) && Plan::ReadIn(e1.at("kx"), "H", 1) && !Plan::ReadIn(e1.at("kx"), "H", 2));
const Json e2 = stored({rec("X", "kx")}, Json::array({part("X", "SEND-READY")}), Json::array({cov("X", "clear")}), Json::array(), e1, 2);
Check("reads: the next round on the same hash is the second read; a relaunch of the same round adds nothing",
Plan::ReadsOf(e2.at("kx")) == 2 && Plan::ReadsOf(stored({rec("X", "kx")}, Json::array({part("X", "SEND-READY")}), Json::array({cov("X", "clear")}), Json::array(), e1).at("kx")) == 1);
const Json e3 = stored({rec("X", "kx")}, Json::array({part("X", "FIX FIRST")}), Json::array({cov("X", "defect")}), Json::array({finding("X", false)}), e2, 3);
Check("reads: a finding on the third read drops the count to nothing", !e3.contains("kx"));
auto need = [](const std::string& lf, const std::string& kind = "block", const Json& unitKind = "code", std::int64_t x = 3) {
Run::Run r;
r.x = x;
return r.ReadsNeeded({{"lensFile", lf}, {"kind", kind}, {"unitKind", unitKind}});
};
Check("reads needed: 3 for a static code unit, 1 for a functional unit, a whole pass, and everything at --reads 1",
need("repo-bug-prover.md") == 3 && need("repo-better-solution.md") == 3 && need(Fn) == 1 && need("repo-better-solution.md", "whole", nullptr) == 1 && need("repo-bug-prover.md", "block", "code", 1) == 1 && Plan::Reads == 3);
// Salvage: an earlier run's output is evidence only if its environment
// was there (exercised), whatever classify says.
{
Run::Run r;
const fs::path sd = td / "old-run";
fs::create_directories(sd / "out");
r.salvageDirs = {sd};
r.testMode = false;
r.x = 1;
r.repo = td;
r.environments = {{Fn, "browser"}, {"repo-bug-prover.md", "none"}};
r.log = noLog;
r.a.dryRun = false;
r.a.noSalvage = false;
const std::string res = R"({"type":"result","subtype":"success","is_error":false,"structured_output":{"verdict":"SEND-READY","findings":[],"notes":[],"coverage":[]},"usage":{"input_tokens":1,"output_tokens":1}})";
const std::string initNo = R"({"type":"system","subtype":"init","model":"m","mcp_servers":[],"tools":["Read"]})";
const std::string initOk = R"({"type":"system","subtype":"init","model":"m","mcp_servers":[{"name":"playwright","status":"connected"}],"tools":["mcp__playwright__browser_snapshot"]})";
const std::string call = R"({"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"mcp__playwright__browser_snapshot","input":{}}]}})"
"\n"
R"({"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"t1","content":"- page"}]}})";
const Json job = {{"label", "repo-functional:whole"}, {"lensFile", Fn}, {"kind", "whole"}, {"model", "m"}};
Write(sd / "out" / "repo-functional:whole.jsonl", std::format("{}\n{}\n", initNo, res));
Check("salvage: a functional whole pass whose server never connected is not salvaged (no browser = no evidence, salvaged or not)", !r.Salvage(job));
Write(sd / "out" / "repo-functional:whole.jsonl", std::format("{}\n{}\n{}\n", initOk, call, res));
std::optional<std::pair<Json, Json>> got = r.Salvage(job);
Check("...with the server connected and a browser call it is, with its environment on the record",
got && got->first.at("environment") == "browser" && Dyn::Truthy(got->first.at("salvaged")) && got->second.at("verdict") == "SEND-READY");
Write(sd / "out" / "repo-bug-prover:block:X.jsonl", std::format("{}\n{}\n", initNo, res));
const Json bj = {{"label", "repo-bug-prover:block:X"}, {"lensFile", "repo-bug-prover.md"}, {"kind", "block"}, {"model", "m"}};
got = r.Salvage(bj);
Check("a static pass (environment none) is salvaged as classified", got && got->first.at("environment") == "none");
// Across environments: the scratch dir's marker says what its passes
// had; no marker = the defaults.
r.environments = {{Fn, "browser"}, {"repo-bug-prover.md", "supabase"}};
const bool unmarked = r.Salvage(bj).has_value();
Write(sd / "environments", R"({"repo-bug-prover": "supabase"})");
r.envMarkers.clear();
const std::optional<std::pair<Json, Json>> marked = r.Salvage(bj);
r.environments = {{Fn, "browser"}, {"repo-bug-prover.md", "none"}};
const bool back = r.Salvage(bj).has_value();
Check("salvage: an output is reused only in the environment its scratch dir's marker names (a dir without one ran the defaults)",
!unmarked && marked && marked->first.at("environment") == "supabase" && !back);
}
// The environment on the STORE side.
const fs::path fx = fs::current_path() / "tests" / "Goldens" / "fixture" / "commits";
const Inventory::Built built = Inventory::Build("commits", fx.string());
Run::Run g;
g.a.artifact = fx.string();
g.a.dryRun = false;
g.a.date = "2026-10-22";
g.units = built.out.at("units");
g.lines = built.lines;
for (const Plan::Lens& l : Plan::Lenses) g.charterSha[std::string(l.file)] = "c";
g.state = State::EmptyState();
g.x = 1;
g.h8 = "H";
g.round = 1;
g.hash = "H";
g.repo = td;
g.statePath = td / "us-env.json";
g.dumpPath = td / "run-env.json";
g.log = noLog;
const std::string U = "p2:hello.py#1";
auto unitJob = [&] {
for (Json j : Plan::PlanLens("repo-bug-prover.md", g.units, g.lines).blocks) {
if (j.at("unit") == U) {
j["gate"] = "G";
return j;
}
}
throw std::runtime_error("no unit job");
};
// One round of the bug-prover on U in env: PrepareBlocks (reuse or a
// job), then - if it ran - a pass with that verdict, stored.
auto round = [&](const std::string& env, const std::string& verdict) -> std::pair<std::string, Json> {
g.environments = Defaults();
g.environments["repo-bug-prover.md"] = env;
g.records.clear();
g.results.clear();
g.wholeParts.clear();
std::map<std::string, std::vector<Json>> parts = {{"repo-bug-prover", {}}};
const std::vector<Json> jobs = g.PrepareBlocks("repo-bug-prover", {unitJob()}, parts);
if (jobs.empty()) return {"reused", nullptr};
const Json& j = jobs.front();
g.records = {{{"label", j.at("label")}, {"lens", "repo-bug-prover"}, {"lensFile", "repo-bug-prover.md"}, {"unit", U}, {"kind", "block"}, {"status", "ok"}, {"reuseKey", j.at("reuseKey")}, {"otherKeys", j.at("otherKeys")}}};
const bool bad = verdict != "SEND-READY";
g.results = {{{"lens", "repo-bug-prover"}, {"coverage", Json::array({{{"item", U}, {"verdict", bad ? "defect" : "clear"}, {"basis", ""}}})}, {"notes", Json::array()},
{"findings", bad ? Json::array({finding(U, false)}) : Json::array()}, {"parts", Json::array({{{"label", j.at("label")}, {"verdict", verdict}}})}}};
g.StoreState();
return {"ran", j};
};
auto inEntries = [&](const Json& key) { return g.state.at("entries").contains(key.get<std::string>()); };
const auto [s1, j1] = round("none", "SEND-READY");
const auto [s2, j2] = round("supabase", "SEND-READY");
Check("store: a clean verdict bought in one environment is not reused in another (none -> supabase runs the unit again); each is stored under its own key",
s1 == "ran" && s2 == "ran" && j1.at("reuseKey") != j2.at("reuseKey") && inEntries(j1.at("reuseKey")) && inEntries(j2.at("reuseKey"))
&& std::ranges::contains(j2.at("otherKeys"), j1.at("reuseKey")));
Check("...and in its own environment it is reused", round("none", "SEND-READY").first == "reused" && round("supabase", "SEND-READY").first == "reused");
g.state["entries"].erase(j2.at("reuseKey").get<std::string>());
const auto [s3, j3] = round("supabase", "FIX FIRST");
Check("store: two environments, one unit, different verdicts - the supabase pass finds a defect the none pass read clean: neither verdict survives, so switching the lens back to none re-reads the unit",
s3 == "ran" && !inEntries(j1.at("reuseKey")) && !inEntries(j3.at("reuseKey")) && round("none", "SEND-READY").first == "ran");
// The whole pass: its key carries the environment, and a whole pass that
// is not clean drops the other environments' whole verdicts.
Json wj = Plan::PlanLens("repo-better-solution.md", g.units, g.lines).whole;
wj["gate"] = "G";
g.environments = Defaults();
g.records.clear();
g.ReuseWhole("repo-better-solution", wj);
const std::string kwNone = wj.at("reuseKey").get<std::string>();
g.state["entries"][kwNone] = {{"lens", "repo-better-solution.md"}, {"unit", nullptr}, {"kind", "whole"}, {"hash", "H"}, {"run", "r"},
{"coverage", Json::array({{{"item", "whole:order"}, {"verdict", "clear"}, {"basis", ""}}, {{"item", "whole:absent"}, {"verdict", "clear"}, {"basis", ""}}})},
{"findings", Json::array()}, {"notes", Json::array()}, {"summary", "s"}};
Json copy = wj;
const bool reusedNone = g.ReuseWhole("repo-better-solution", copy).has_value();
g.environments["repo-better-solution.md"] = "supabase";
Json ws = wj;
const bool reusedSup = g.ReuseWhole("repo-better-solution", ws).has_value();
g.records = {{{"label", ws.at("label")}, {"lens", "repo-better-solution"}, {"lensFile", "repo-better-solution.md"}, {"unit", nullptr}, {"kind", "whole"}, {"status", "ok"}, {"reuseKey", ws.at("reuseKey")}, {"otherKeys", ws.at("otherKeys")}}};
g.results = {{{"lens", "repo-better-solution"}, {"coverage", Json::array()}, {"findings", Json::array({finding("whole:order", false)})}, {"notes", Json::array()}, {"parts", Json::array()}}};
g.wholeParts = {{"repo-better-solution", {{"verdict", "FIX FIRST"}, {"coverageMissing", Json::array()}, {"status", "ok"}}}};
g.StoreState();
Check("store: the whole pass - a clean whole verdict is reused in its environment and not in another; a whole pass that is not clean in supabase drops the clean one none held",
reusedNone && !reusedSup && ws.at("reuseKey") != kwNone && std::ranges::contains(ws.at("otherKeys"), Json(kwNone)) && !g.state.at("entries").contains(kwNone));
// Resume across environments: the checkpoint's `environments` (none in a
// checkpoint written before 2026-10-16.1 = the defaults).
g.resume = {{"run", "r1"},
{"results", Json::array({{{"lens", "repo-bug-prover"}, {"verdict", "SEND-READY"}, {"findings", Json::array()}, {"coverageMissing", Json::array()}, {"parts", Json::array()}}})},
{"environments", {{"repo-bug-prover", "supabase"}}}};
g.newRound = false;
g.records.clear();
g.environments = Defaults();
const bool no = g.Resumable("repo-bug-prover", "repo-bug-prover.md", "G").has_value();
g.environments["repo-bug-prover.md"] = "supabase";
const std::optional<Json> yes = g.Resumable("repo-bug-prover", "repo-bug-prover.md", "G");
g.resume.erase("environments");
const bool old = g.Resumable("repo-bug-prover", "repo-bug-prover.md", "G").has_value();
g.environments["repo-bug-prover.md"] = "none";
const bool oldDefault = g.Resumable("repo-bug-prover", "repo-bug-prover.md", "G").has_value();
Check("resume: a lens result is resumed only in the environment it was bought in (an old checkpoint without environments = the defaults)",
!no && yes && yes->at("verdict") == "SEND-READY" && !old && oldDefault);
// The preflight of a gate with the database under test.
const fs::path repo = td / "mig";
fs::create_directories(repo / "supabase" / "migrations");
for (const std::string_view f : {"20261001000000_a.sql", "20261002000000_b.sql"}) Write(repo / "supabase" / "migrations" / f, "select 1;\n");
Write(repo / "supabase" / "migrations" / "README", "not a migration\n");
Git(repo, {"init", "-q"});
Git(repo, {"add", "."});
Git(repo, {"commit", "-qm", "base"});
const std::string mbase = Git(repo, {"rev-parse", "HEAD"});
Write(repo / "supabase" / "migrations" / "20261002000000_b.sql", "-- b, now with a filter\nselect 2 where true;\n");
Git(repo, {"commit", "-qam", "the range changes b"});
g.trees = {repo.string()};
g.a.base = mbase;
// What the CLI records: the statements, its own spacing, no comment.
const std::string appliedA = "select 1";
const std::string appliedB = "select 2 where true";
using Rows = std::vector<std::vector<std::optional<std::string>>>;
auto rows = [](Rows r) { return [r](std::string_view, std::string_view) { return Run::QueryResult({"version", "s"}, r); }; };
g.query = rows({{"20261001000000", appliedA}, {"20261002000000", appliedB}});
const auto same = g.MigrationsDrift("u");
g.query = rows({{"20261001000000", appliedA}});
const auto behind = g.MigrationsDrift("u");
g.query = rows({{"20261001000000", appliedA}, {"20261002000000", appliedB}, {"20260901000000", "x"}});
const auto ahead = g.MigrationsDrift("u");
g.query = rows({{"20261001000000", "select 1 -- other text, a base migration: versions alone"}, {"20261002000000", "select 1"}});
const auto stale = g.MigrationsDrift("u");
g.query = [](std::string_view, std::string_view sql) -> Run::QueryResult {
if (sql.contains("statements")) throw PgWire::SqlError({{'C', "42703"}, {'M', "column \"statements\" does not exist"}});
return {{"version", "s"}, {{"20261001000000", std::nullopt}, {"20261002000000", std::nullopt}}};
};
const auto oldCli = g.MigrationsDrift("u");
g.query = [](std::string_view, std::string_view) -> Run::QueryResult { throw PgWire::SqlError({{'C', "42P01"}, {'M', "relation \"supabase_migrations.schema_migrations\" does not exist"}}); };
const auto plain = g.MigrationsDrift("u");
auto has = [](const std::optional<std::string>& text, std::string_view part) { return text && text->contains(part); };
Check("preflight: the database's applied migrations must be the range HEAD's - one not applied, one applied that HEAD lacks, or a migration of the range applied with other content (amended, the stack not reset) is evidence drift",
!same.first && has(same.second, "2 applied migrations") && has(same.second, "1 of the range compared by content") && has(behind.first, "at HEAD, not applied: 20261002000000")
&& has(ahead.first, "applied, not at HEAD: 20260901000000") && has(stale.first, "applied with other content than HEAD has: 20261002000000") && !has(stale.first, "20261001000000"),
std::format("{} | {} | {} | {}", same.second.value_or("-"), behind.first.value_or("-"), ahead.first.value_or("-"), stale.first.value_or("-")));
Check("preflight: the content is compared blind to comments, whitespace and semicolons; an old CLI without the statements column compares versions; a database without the table is a note",
PgWire::Fingerprint("-- b, now with a filter\nselect 2 where true;\n") == PgWire::Fingerprint("select 2 where true") && PgWire::Fingerprint("select 2 where true") != PgWire::Fingerprint("select 2 where false")
&& PgWire::Fingerprint("select '--not a comment'") != PgWire::Fingerprint("select") && !oldCli.first && !plain.first && has(plain.second, "not compared"));
g.trees = {td.string()};
Check("preflight: a tree without supabase/migrations/ at HEAD compares nothing", !g.MigrationsDrift("u").first);
g.query = [](std::string_view url, std::string_view sql) { return PgWire::Query(url, sql); };
g.cfg = {{"supabase", {{"dbUrl", "postgresql://postgres:postgres@127.0.0.1:1/postgres"}, {"start", nullptr}, {"stop", nullptr}, {"notes", ""}}},
{"functional", {{"start", "bash x.sh full"}, {"readyPath", "/"}, {"exclude", Json::array()}}},
{"slots", {{"dir", nullptr}, {"start", nullptr}, {"stop", nullptr}, {"memMb", nullptr}}}};
g.slotMode = false;
g.slotProblems.clear();
g.loginFixture = std::nullopt;
g.stacks = std::make_unique<Pool::Stacks>(std::vector<Json> {
{{"id", "the stack"}, {"appUrl", "http://localhost:1"}, {"apiUrls", Json::array()}, {"dbUrl", g.cfg.at("supabase").at("dbUrl")}, {"login", nullptr}, {"slot", false}}});
g.environments = Defaults();
g.environments["repo-bug-prover.md"] = "supabase";
std::vector<std::string> logs;
g.log = [&](const std::string& line) { logs.push_back(line); };
auto preflight = [&](const std::string& harness, bool dryRun) -> std::pair<std::optional<std::int32_t>, std::string> {
g.a.harness = harness;
g.a.dryRun = dryRun;
try {
g.InstrumentPreflight(std::vector<std::string> {"repo-bug-prover.md"});
return {std::nullopt, ""};
} catch (const Run::Preflight& e) {
return {3, std::format("preflight: {}\n", e.what())};
}
};
const auto [refusedOc, whyOc] = preflight("opencode", true);
preflight("claude", true);
const auto [dead, whyDead] = preflight("claude", false);
Check("preflight: the opencode harness refuses a lens with the database (exit 3); a dry run names the masked URL and probes nothing; a database that does not answer stops the run before its gate (exit 3)",
refusedOc == 3 && whyOc.contains("needs the claude harness") && std::ranges::any_of(logs, [](const std::string& x) { return x.contains("dry run") && x.contains("postgres:***@"); }) && dead == 3
&& whyDead.contains("does not answer") && whyDead.contains("(bash x.sh full)") && !whyDead.contains(":postgres@"),
whyDead);
fs::remove_all(td);
return Failures == 0 ? 0 : 1;
}