test(run): full runs end to end on a claude stand-in

The Runs test drives this binary as crafter-review on a scratch repository,
with a /bin/sh claude that answers every pass from its system file and
prompt: a dry run; a run SIGKILLed in its last gate (every pass goes with
it through its supervisor); the next run resuming the first gate from the
checkpoint, salvaging the killed run's outputs and buying only the pass that
never answered; a run with nothing left to buy; a FIX FIRST with
--no-reuse --no-salvage; a stop by the stop file and by SIGTERM, each
leaving no process behind; a stale stop file dropped by the run that claims
its dir; a stop between gates.

Two fixes it led to:
- A stop that lands while a gate's pool is set up is no longer lost: the
  stop is checked under the same lock that publishes the pool.
- A run never takes the number of a dump on record. Past a killed run that
  left no dump, a later run that bought nothing could have its number
  taken again and its dump written over.

Drivers gains the --no-reuse store check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 09:59:12 +01:00
commit 952eb13f5c
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 455 additions and 11 deletions

View file

@ -4,8 +4,7 @@
// Several drivers at once: the board (seats, memory, the cap), one full run
// per hash, one driver per scratch dir, the merged unit-state, the locked
// ledger, the shared memory record. The checks that run the Scheduler,
// Admission, Stacks, the stack keys and the run's state store come with the
// pool and the driver.
// Admission, Stacks and the stack keys are in the Scheduler test.
//
// Another driver is a live process of its own (`sleep 300`), so its entries
// stand until it dies; two drivers recording into one memory file are two
@ -449,6 +448,33 @@ namespace {
const Json old = {{"version", 1}, {"entries", {{"q", {{"unit", "Q"}}}}}, {"personaPass", nullptr}};
State::SaveWhole(sp, old);
Check("...sets=None is the whole-file write as before", Dyn::Equal(Dyn::LoadJsonFile(sp), old));
{
Run::Run r;
r.records = {{{"label", "L:block:X"}, {"lens", "L"}, {"lensFile", "repo-bug-prover.md"}, {"unit", "X"}, {"kind", "block"}, {"status", "ok"}, {"reuseKey", "kx"}}};
r.hash = "H";
r.h8 = "H";
r.round = 1;
r.dumpPath = td / "run-1.json";
r.repo = td;
r.results = {{{"lens", "L"},
{"coverage", Json::array({Json {{"item", "X"}, {"verdict", "clear"}, {"basis", ""}}})},
{"findings", Json::array()},
{"notes", Json::array()},
{"parts", Json::array({Json {{"label", "L:block:X"}, {"kind", "block"}, {"unit", "X"}, {"verdict", "SEND-READY"}, {"findings", 0}}})}}};
const fs::path sp2 = td / "unit-state-nr.json";
const Json kx = {{"unit", "X"}, {"hash", "H"}, {"run", "run-0.json"}, {"reads", Json::array({Json {{"hash", "H"}, {"round", 0}, {"run", "run-0.json"}}})}};
State::SaveWhole(sp2, {{"version", 1}, {"entries", {{"kx", kx}, {"ko", {{"unit", "O"}}}}}, {"personaPass", nullptr}});
r.state = {{"version", 1}, {"entries", Json::object()}, {"personaPass", nullptr}};
r.statePath = sp2;
r.a.date = "2026-10-31";
r.a.noReuse = true;
r.log = [](const std::string&) {};
const Json merged = r.StoreState();
Check("a --no-reuse run: the store returns the merged file (its own read on top of the file's, another unit's entry kept) and keeps its empty view for the reuse decisions",
Plan::ReadsOf(merged.at("entries").at("kx")) == 2 && merged.at("entries").contains("ko") && r.state.at("entries").empty()
&& Plan::ReadsOf(State::LoadState(sp2).at("entries").at("kx")) == 2,
merged.dump());
}
// The ledger: two drivers on one file, every write a locked delta.
const fs::path lp = td / "seat-ledger.json";