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

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;
}