feat(run): the repository under review is the freeze record's, not the working directory's
A directory can review repositories other than itself: a workbench whose config, env file and records serve several, freezing each with --repo and --out. The run took the working directory for the repository under review: the prompt named it, the calibration was kept under it, and --trees fell back to it. The run now reads the repository from the artifact's freeze.json - the prompt names it and the calibration is kept under it - and --trees defaults to what the record says: the worktree, else the repository when it is not the working directory, else `.`, the same rule that builds the printed run line. The working directory keeps its roles: the config, the env file, the slots, the lens processes' cwd and every logged path. A damaged record stops the run at preflight.
This commit is contained in:
parent
ea4d362a10
commit
4fc15fe1c2
9 changed files with 133 additions and 14 deletions
|
|
@ -170,6 +170,28 @@ int main() {
|
|||
g({"merge", "-q", "--no-ff", "-m", "merge side", "side"});
|
||||
const Json r3 = freeze({.base = base, .replace = true});
|
||||
Check("merge in the range refused", !r3.at("ok").get<bool>() && AnyContains(r3.at("failures"), "merge commit"));
|
||||
// What a run reads from the record: the repository under review and the
|
||||
// tree the lenses read (a workbench freezing another repository with
|
||||
// --repo: the record names it, not the working directory).
|
||||
const Json withTree = {{"repo", repo.string()}, {"worktree", "x/y"}};
|
||||
const Json repoOnly = {{"repo", repo.string()}};
|
||||
Check("TreeOf: the record's worktree, else the repository when it is not the working directory, else `.`",
|
||||
Freeze::TreeOf(withTree, td) == "x/y" && Freeze::TreeOf(repoOnly, td) == repo.string() && Freeze::TreeOf(repoOnly, repo) == "." && Freeze::TreeOf(Json::object(), td) == ".");
|
||||
const Freeze::Reviewed byRecord = Freeze::ReviewedBy(out, td);
|
||||
const fs::path none = td / "no-record";
|
||||
fs::create_directories(none);
|
||||
const Freeze::Reviewed byNone = Freeze::ReviewedBy(none, td);
|
||||
std::ofstream(none / "freeze.json", std::ios::binary) << "{broken";
|
||||
bool damaged = false;
|
||||
try {
|
||||
Freeze::ReviewedBy(none, td);
|
||||
} catch (const std::runtime_error& e) {
|
||||
damaged = std::string(e.what()).contains("not a freeze record");
|
||||
}
|
||||
Check("ReviewedBy: the record's repository and its tree; no record: the working directory and `.`; a damaged record throws, saying so",
|
||||
fs::weakly_canonical(byRecord.repo) == fs::weakly_canonical(repo) && byRecord.tree == Dyn::AbsPath(repo.string()) && Freeze::ReviewedBy(out, repo).tree == "." && byNone.repo == td
|
||||
&& byNone.tree == "." && damaged,
|
||||
std::format("{} {} {}", byRecord.repo.string(), byRecord.tree, byNone.tree));
|
||||
g({"worktree", "remove", "--force", wt.string()});
|
||||
fs::remove_all(td);
|
||||
return Failures ? 1 : 0;
|
||||
|
|
|
|||
|
|
@ -448,6 +448,51 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\"
|
|||
std::format("exit {}, {} call(s), {}", truth.returnCode, calls() - before, dumpT.is_object() ? Text(dumpT.at("overall")) : truth.out + truth.err));
|
||||
}
|
||||
|
||||
// A workbench (2026-10-31): a directory that reviews another
|
||||
// repository - its own config and env file, the artifact wherever
|
||||
// --out says, the lenses' tree the frozen worktree. The run reads the
|
||||
// repository under review from the freeze record: the prompt names
|
||||
// it, not the working directory, and --trees defaults to the
|
||||
// recorded worktree.
|
||||
{
|
||||
const fs::path bench = base / "bench";
|
||||
fs::create_directories(bench / ".review-gates");
|
||||
Write(bench / ".review-gates" / "config.json", R"({"lenses": {"repo-bug-prover": {"run": true}}})");
|
||||
const fs::path out = bench / "journal" / "push" / "y";
|
||||
const Process::Captured benchFreeze = Process::Run({Platform::SelfExe().string(), "freeze", "--repo", repo.string(), "--out", out.string(), "--worktree", "--base", baseSha},
|
||||
bench, std::chrono::seconds(60), {}, Env);
|
||||
Json benchRecord;
|
||||
try {
|
||||
benchRecord = Dyn::LoadJsonFile(out / "freeze.json");
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
const std::string benchHash = benchRecord.is_object() ? Text(benchRecord.at("hash")) : std::string();
|
||||
const std::string benchTree = benchRecord.is_object() ? Text(Dyn::Get(benchRecord, "worktree")) : std::string();
|
||||
Check("a workbench freezes another repository: the record names it, and the worktree is under the workbench's .review-gates/trees/",
|
||||
benchFreeze.returnCode == 0 && benchRecord.is_object() && Text(benchRecord.at("repo")) == Dyn::AbsPath(repo.string())
|
||||
&& benchTree == std::format(".review-gates/trees/repo-{}", benchHash.substr(0, 8)) && fs::is_directory(bench / benchTree),
|
||||
benchFreeze.out + benchFreeze.err);
|
||||
const fs::path benchScratch = base / "bench-scratch";
|
||||
const Process::Captured benchRun = Process::Run({Platform::SelfExe().string(), "run", "--artifact", "journal/push/y", "--hash", benchHash, "--base", baseSha, "--dispositions",
|
||||
"journal/push/y/gate-state/dispositions.json", "--date", Text(freezeJson.at("date")), "--claude-bin", fake.string(), "--scratch",
|
||||
benchScratch.string(), "--seats", "local", "--no-probe", "--dry-run"},
|
||||
bench, std::chrono::seconds(60), {}, Env);
|
||||
const fs::path benchDry = benchScratch / std::format("{}-run-1-dry", benchHash.substr(0, 8));
|
||||
// The run layer is in the system prompts (system-<sha8>.md).
|
||||
std::string systems;
|
||||
std::error_code benchError;
|
||||
for (fs::directory_iterator it(benchDry, benchError), end; !benchError && it != end; it.increment(benchError)) {
|
||||
const std::string name = it->path().filename().string();
|
||||
if (name.starts_with("system-") && name.ends_with(".md")) systems += Platform::ReadFile(it->path()).value_or("");
|
||||
}
|
||||
const std::string benchLog = Platform::ReadFile(benchDry / "log.txt").value_or("");
|
||||
Check("...and a dry run from the workbench without --trees (exit 2, as every dry run) reads the recorded worktree, names the repository under review in the run layer, not the workbench, and logs it",
|
||||
benchRun.returnCode == 2 && !systems.empty()
|
||||
&& systems.contains(std::format("Repository under review (repo), checked out at the range HEAD (the last commit of the artifact): {} -", benchTree)) && !systems.contains("(bench)")
|
||||
&& benchLog.contains(std::format("repository under review: {} (read at {})", Dyn::AbsPath(repo.string()), benchTree)),
|
||||
std::format("exit {}, {} bytes of system prompt; {}{}", benchRun.returnCode, systems.size(), benchRun.out, benchRun.err));
|
||||
}
|
||||
|
||||
// A stop between two gates: the next gate's pool starts aborted and
|
||||
// runs nothing of it.
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue