feat(modes): the kernel modes - a patch series and a mailing-list reply

Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.

The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
This commit is contained in:
Jorijn van der Graaf 2026-10-31 22:56:12 +01:00
commit 339f4b9b40
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
46 changed files with 5732 additions and 362 deletions

View file

@ -173,8 +173,10 @@ namespace Crafter::Review::Run {
return {
{"config", opt(a.config)},
{"mode", a.mode},
{"subsystem", opt(a.subsystem)},
{"artifact", a.artifact},
{"hash", opt(a.hash)},
{"reviewers", a.reviewers},
{"dispositions", opt(a.dispositions)},
{"base", a.base},
{"date", a.date},
@ -189,6 +191,7 @@ namespace Crafter::Review::Run {
{"reads", a.reads},
{"no_reuse", a.noReuse},
{"no_salvage", a.noSalvage},
{"persona_blocks", a.personaBlocks},
{"dry_run", a.dryRun},
{"claude_bin", opt(a.claudeBin)},
{"models", a.models},
@ -332,6 +335,10 @@ namespace Crafter::Review::Run {
environments = Config::Environments(cfg);
}
bool Run::Kernel() const {
return Plan::IsKernelMode(a.mode);
}
Run::~Run() {
// atexit's board.leave: everything of this driver off the board.
if (board) board->Leave();
@ -373,21 +380,29 @@ namespace Crafter::Review::Run {
// ---- Gate 0 -------------------------------------------------------------------------
void Run::Gate0() {
if (!fs::exists(a.artifact)) Die(std::format("artifact {} does not exist", a.artifact));
hash = Artifact::ArtifactHash("commits", a.artifact);
hash = Artifact::ArtifactHash(a.mode, a.artifact);
if (a.hash && !a.hash->empty() && *a.hash != hash) Die(std::format("--hash {} does not match the artifact ({})", Dyn::Slice(*a.hash, 0, 12), hash.substr(0, 12)));
h8 = hash.substr(0, 8);
const std::optional<std::string> patchHead = Artifact::PatchHead(a.artifact);
if (!patchHead || patchHead->empty()) Die("the last patch has no `From <sha>` line - not a git format-patch export (use `freeze`)");
head = *patchHead;
for (const std::string& t : trees) {
if (!Artifact::IsToplevel(t)) Die(std::format("--trees: {} is not the toplevel of a git checkout", t));
if (a.mode == "commits") {
// The lenses read the live checkout named first in --trees: it
// must be the range HEAD the artifact was cut from, and clean -
// anything else is evidence the review never saw (re-checked
// before every launch).
const std::optional<std::string> patchHead = Artifact::PatchHead(a.artifact);
if (!patchHead || patchHead->empty()) Die("the last patch has no `From <sha>` line - not a git format-patch export (use `freeze`)");
head = *patchHead;
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");
// A reply's gate-state sits beside the draft; a directory artifact's
// inside it.
gateDir = Dyn::JoinPath(a.mode == "reply" ? Dyn::DirName(a.artifact) : a.artifact, "gate-state");
fs::create_directories(gateDir);
Inventory::Built built;
try {
built = Inventory::Build("commits", a.artifact);
built = Inventory::Build(a.mode, a.artifact);
} catch (const Inventory::InvariantError& e) {
Die(e.what());
} catch (const Dyn::Error& e) {
@ -418,16 +433,16 @@ namespace Crafter::Review::Run {
Json holder;
if (!a.dryRun && !testMode) {
try {
runLock = std::make_unique<State::RunLock>(gateDir, "commits", h8);
runLock = std::make_unique<State::RunLock>(gateDir, a.mode, h8);
} catch (const State::RunLocked& e) {
const std::string how = Platform::InSandbox() ? std::format("wait for it or stop it (`{} stop <its scratch dir>`)", Invocation()) : std::string("wait for it or kill it");
Die(std::format("a full run on this hash is already live in {}: {} - {}; a dead holder releases the lock", Dyn::RelPath(gateDir.string(), repo.string()), e.what(), how));
}
} else {
const std::optional<Json> found = State::LockHolder(gateDir, "commits", h8);
const std::optional<Json> found = State::LockHolder(gateDir, a.mode, h8);
if (found) holder = *found;
}
std::int64_t runN = State::NextRunNumber(gateDir, "commits", h8);
std::int64_t runN = State::NextRunNumber(gateDir, a.mode, h8);
if (Dyn::Truthy(holder) && Dyn::Truthy(Dyn::Get(holder, "run_n"))) runN = std::max(runN, Int(holder.at("run_n")) + 1);
const fs::path scratchRoot = ScratchRoot(a.scratch);
// A run killed before its first checkpoint left no dump but did leave
@ -445,7 +460,7 @@ namespace Crafter::Review::Run {
// run that bought nothing (no outputs in its dir) would otherwise have
// its number taken again and its dump written over.
auto dumped = [&](std::int64_t n) {
return fs::exists(gateDir / std::format("commits-{}-run-{}.json", h8, n)) || fs::exists(gateDir / std::format("commits-{}-run-{}-test.json", h8, n));
return fs::exists(gateDir / std::format("{}-{}-run-{}.json", a.mode, h8, n)) || fs::exists(gateDir / std::format("{}-{}-run-{}-test.json", a.mode, h8, n));
};
while (!a.dryRun) {
const fs::path candidate = scratchRoot / std::format("{}-run-{}", h8, runN);
@ -473,7 +488,7 @@ namespace Crafter::Review::Run {
const std::lock_guard guard(shared);
stopFile = scratch / "stop";
}
dumpPath = a.dryRun ? scratch / "dry-run.json" : gateDir / std::format("commits-{}-run-{}{}.json", h8, runN, testMode ? "-test" : "");
dumpPath = a.dryRun ? scratch / "dry-run.json" : gateDir / std::format("{}-{}-run-{}{}.json", a.mode, h8, runN, testMode ? "-test" : "");
if (runLock) {
runLock->WriteInfo({{"pid", Platform::PublishedPid()},
{"since", Platform::LocalTime("%Y-%m-%dT%H:%M:%S")},
@ -482,25 +497,33 @@ namespace Crafter::Review::Run {
{"run_n", runN}});
}
logFile = scratch / "log.txt";
log(std::format("crafter-review {} prompt v{}: commits {} @ {}", Version(), PromptVersion, Dyn::RelPath(a.artifact, repo.string()), hash));
log(std::format("crafter-review {} prompt v{}: {} {} @ {}", Version(), PromptVersion, a.mode, Dyn::RelPath(a.artifact, repo.string()), hash));
std::error_code canonical;
if (fs::weakly_canonical(reviewed, canonical) != fs::weakly_canonical(repo, canonical)) log(std::format("repository under review: {} (read at {})", reviewed.string(), trees.front()));
if (a.mode == "commits" && fs::weakly_canonical(reviewed, canonical) != fs::weakly_canonical(repo, canonical)) log(std::format("repository under review: {} (read at {})", reviewed.string(), trees.front()));
log(std::format("config: {}", Dyn::Truthy(Dyn::Get(cfg, "_path")) ? Dyn::Str(cfg.at("_path")) : std::string("defaults (no .review-gates/config.json)")));
for (const Json& w : Dyn::Or(Dyn::Get(cfg, "_warnings"), Dyn::EmptyList())) log(std::format("config: warning: {}", Dyn::Str(w)));
// The mode's lenses: the others are another mode's and say nothing
// about this run.
const std::vector<std::string> modeLenses = Plan::ModeLenses(a.mode);
std::vector<std::string> lensParts;
for (const Plan::Lens& lens : Plan::Lenses) {
const std::string lf(lens.file);
for (const std::string& lf : modeLenses) {
const std::string name = Plan::LensName(lf);
lensParts.push_back(std::ranges::contains(off, lf) ? std::format("{} OFF (lenses.{}.run = false)", name, name) : std::format("{} ({})", name, Dyn::Str(Dyn::At(environments, lf))));
}
log(std::format("lenses: {}", Dyn::Join(lensParts, ", ")));
std::vector<std::string> running;
for (const Plan::Lens& lens : Plan::Lenses) {
if (!std::ranges::contains(off, std::string(lens.file))) running.emplace_back(lens.file);
for (const std::string& lf : modeLenses) {
if (!std::ranges::contains(off, lf)) running.push_back(lf);
}
const std::vector<std::string> exclude = Strings(cfg.at("functional").at("exclude"));
notReviewed = Plan::NotReviewed(units, lines, running, exclude);
log(std::format("inventory: {} units ({}); {} units no lens that runs reads", units.size(), Dyn::RelPath(invPath.string(), repo.string()), notReviewed.size()));
if (Kernel()) {
// The kernel modes excuse nothing: a unit no lens owns is a hole.
notReviewed.clear();
log(std::format("inventory: {} units ({})", units.size(), Dyn::RelPath(invPath.string(), repo.string())));
} else {
notReviewed = Plan::NotReviewed(units, lines, running, exclude);
log(std::format("inventory: {} units ({}); {} units no lens that runs reads", units.size(), Dyn::RelPath(invPath.string(), repo.string()), notReviewed.size()));
}
if (a.units) {
std::set<std::string> known;
for (const Json& u : units) known.insert(Dyn::Str(u.at("id")));
@ -511,11 +534,49 @@ namespace Crafter::Review::Run {
if (!unknown.empty()) Die(std::format("--units names ids not in the inventory: {}", Dyn::Join(unknown, ", ")));
}
dispEntries = Dispositions::Load(a.dispositions.value_or(""));
dispRows = Dispositions::Classify(dispEntries, hash, a.artifact);
dispRows = Dispositions::Classify(dispEntries, hash, a.artifact, a.mode);
inForce = Dispositions::InForce(dispEntries, dispRows);
std::vector<std::string> rowParts;
for (const Json& r : dispRows) rowParts.push_back(std::format("{}:{}", Dyn::Str(r.at("id")), Dyn::Str(r.at("status"))));
log(std::format("dispositions: {} in force of {} ({})", inForce.size(), dispEntries.size(), rowParts.empty() ? std::string("none") : Dyn::Join(rowParts, ", ")));
if (Kernel()) {
// The references the artifact cites, prefetched into the
// gate-state (a dry run reads an existing index and fetches
// nothing), and each reviewer's calibration map.
const std::string text = a.mode == "reply" ? Dyn::ReadText(fs::path(a.artifact)) : Dyn::Join(lines, "\n");
refUrls = Refs::ParseRefs(text);
const fs::path refsDir = gateDir / "refs";
const fs::path indexPath = gateDir / std::format("refs-{}.json", h8);
if (a.dryRun && fs::exists(indexPath)) {
refIndex = Dyn::LoadJsonFile(indexPath);
log(std::format("refs: {} entries loaded from the existing index (dry run, no fetch)", refIndex.size()));
} else {
refIndex = Refs::Prefetch(repo, refUrls, refsDir, indexPath, log);
}
refDigest = Dyn::Truthy(refIndex) ? Refs::Digest(refIndex) : std::string("REFERENCES prefetched: (none - the artifact cites no lore URLs)");
const std::string since = Refs::SinceOf(a.date);
corpora.clear();
for (const std::string& name : reviewers) {
auto [path, corpus] = Refs::PersonaCorpus(repo, name, refsDir, since, log);
if (corpus.empty()) {
// A private contributor (no lore record): their mail is
// indexed in the ledger's live-threads table.
const std::string s = a.subsystem.value_or("");
corpus = std::format("lore.py search for mail from {} since {}: NO RESULTS - this person has no mail on lore in the last year. "
"Their mail is PRIVATE: the Live threads table in journal/{}/ledger.md indexes every message with its capture path "
"under journal/{}/captures/, and their submissions are frozen under journal/{}/patch/. Calibrate on those, verbatim.",
name, since, s, s, s);
}
corpora[name] = corpus;
}
anchorText.clear();
for (const Json& u : units) {
if (u.at("id") == "anchor") {
anchorText = Plan::UnitText(lines, u);
break;
}
}
}
statePath = gateDir / "unit-state.json";
state = a.noReuse ? State::EmptyState() : State::LoadState(statePath);
salvageDirs.clear();
@ -551,7 +612,7 @@ namespace Crafter::Review::Run {
round = (Dyn::Truthy(r) ? Int(r) : 1) + (newRound ? 1 : 0);
}
if (!a.dryRun) WriteText(scratch / "round", std::to_string(round));
log(std::format("reads: every code unit of the static code lenses needs {} clean reads, one per round; this is round {} on this hash{}", x, round,
log(std::format("reads: every code unit of the {}code lenses needs {} clean reads, one per round; this is round {} on this hash{}", Kernel() ? "" : "static ", x, round,
newRound || !sameHash ? std::string() : std::string(" (continuing an unfinished run)")));
}
for (const Plan::Lens& lens : Plan::Lenses) {
@ -559,17 +620,18 @@ namespace Crafter::Review::Run {
charters[lf] = Prompt::Charter(lf);
charterSha[lf] = Plan::Sha(charters[lf]);
}
systemText = Prompt::SystemText();
systemText = Prompt::SystemText(a.mode);
systemPaths.clear();
systemByLens.clear();
runLayer = Prompt::RunLayer(Dyn::RelPath(a.artifact, repo.string()), a.base, a.date, trees, hash, lines, units, contextPaths, Basename(reviewed));
runLayer = Kernel() ? Prompt::RunLayerKernel(a.mode, Dyn::RelPath(a.artifact, repo.string()), a.subsystem.value_or(""), a.base, reviewers, a.date, trees, hash, lines, units, refDigest, contextPaths)
: Prompt::RunLayer(Dyn::RelPath(a.artifact, repo.string()), a.base, a.date, trees, hash, lines, units, contextPaths, Basename(reviewed), a.subsystem.value_or(""));
// The application under test: the login fixture, the TOTP helper.
const Json& fcfg = cfg.at("functional");
loginFixture = Config::LoginFixture(cfg, repo);
stacks = LoadStacks();
const Json first = *stacks->First();
appBlock = Prompt::AppBlock(fcfg, loginFixture, Pool::TotpCommand(loginFixture), slotMode);
schemas = {{"block", Schema::SchemaJson("block")}, {"whole", Schema::SchemaJson("whole")}};
schemas = {{"block", Schema::SchemaJson("block", a.mode)}, {"whole", Schema::SchemaJson("whole", a.mode)}};
// Memory: every pass's tree measured, its peak recorded per repository
// and environment; the budget per gate.
calibration = std::make_unique<Memory::Calibration>(scratchRoot / "memory.json", reviewed, DefaultCosts());
@ -652,7 +714,12 @@ namespace Crafter::Review::Run {
}
const Json pc = SeatPool::PoolConfig(processEnv, a.effort);
board = std::make_unique<SeatPool::Board>(SeatPool::BoardPath(), std::string(), std::nullopt, log);
board->Join({{"run", std::format("commits {} @ {}", Basename(Dyn::RStrip(a.artifact, "/")), h8)},
std::vector<std::string> runParts = {a.mode};
if (a.subsystem && !a.subsystem->empty()) runParts.push_back(*a.subsystem);
runParts.push_back(Basename(Dyn::RStrip(a.artifact, "/")));
runParts.emplace_back("@");
runParts.push_back(h8);
board->Join({{"run", Dyn::Join(runParts, " ")},
{"dump", Basename(dumpPath)},
{"scratch", scratch.string()},
{"effort", a.effort},
@ -1061,7 +1128,10 @@ namespace Crafter::Review::Run {
}
const Plan::Environment& env = *Plan::FindEnvironment(Dyn::Str(Dyn::At(environments, lensFile)));
const std::string db = env.db ? Prompt::DbBlock(cfg.at("supabase"), Pool::SqlCommand(), dbRole, env.app, dbChecked, dbBypass) : std::string();
const std::string ll = Prompt::LensLayer(lensFile, charters.at(lensFile), Dispositions::Block(inForce, dispRows, lensFile, hash), env.app ? appBlock : std::string(), summary, db);
const std::string persona = Dyn::StrOr(Dyn::Get(job, "persona"), "");
const auto corpus = corpora.find(persona);
const std::string ll = Prompt::LensLayer(lensFile, charters.at(lensFile), Dispositions::Block(inForce, dispRows, lensFile, hash, a.subsystem.value_or("")), env.app ? appBlock : std::string(), summary, db,
persona, corpus == corpora.end() ? std::string() : corpus->second);
lensLayersBytes[lens] = static_cast<std::int64_t>(Dyn::Length(ll));
const std::string sysText = Prompt::SystemPrompt(systemText, runLayer, ll);
const std::string sha = Plan::Sha(sysText);
@ -1084,7 +1154,7 @@ namespace Crafter::Review::Run {
const Json& login = Dyn::Get(stack, "login");
stackText = Prompt::StackText(stack, Pool::TotpCommand(login.is_null() ? std::nullopt : std::optional<std::string>(Dyn::Str(login))));
}
return {found->second, Prompt::UserPrompt(Prompt::Tail(job, units, lines, summary), job, stackText)};
return {found->second, Prompt::UserPrompt(Prompt::Tail(job, units, lines, summary), job, stackText, Kernel())};
}
std::optional<std::string> Run::CacheKey(Json& job) {
@ -1099,7 +1169,16 @@ namespace Crafter::Review::Run {
// ---- reuse --------------------------------------------------------------------------
const Plan::CommitCode& Run::RangeCode() {
if (!commitCode) commitCode = Plan::CommitCodeOf(Artifact::PatchTexts(a.artifact));
if (!commitCode) {
commitCode = Plan::CommitCodeOf(Artifact::PatchTexts(a.artifact), a.mode == "series");
if (a.mode == "series" && !a.base.empty()) {
// A kernel export carries a base-commit line only when it was
// cut with --base, and then in the cover letter: the
// submission base the run was given is part of every code
// key as well.
commitCode->base = commitCode->base.empty() ? a.base : std::format("{},{}", commitCode->base, a.base);
}
}
return *commitCode;
}
@ -1108,6 +1187,13 @@ namespace Crafter::Review::Run {
// a part, re-normalised against today's dispositions, with its summary
// for the block passes. nullopt: the whole pass runs.
const std::string lensFile = Dyn::Str(job.at("lensFile"));
if (a.mode != "commits") {
// Only the push gates reuse a whole pass; a kernel whole pass
// runs on every hash.
job["reuseKey"] = nullptr;
job["otherKeys"] = Json::array();
return std::nullopt;
}
std::vector<std::string> ids;
for (const Json& d : inForce) ids.push_back(Dyn::Str(d.at("id")));
std::map<std::string, std::optional<std::string>> keys;
@ -1164,6 +1250,14 @@ namespace Crafter::Review::Run {
for (const Json& d : inForce) ids.push_back(Dyn::Str(d.at("id")));
const Json& configured = Dyn::Get(environments, lensFile);
const std::string env = environment && !environment->empty() ? *environment : (configured.is_null() ? std::string() : Dyn::Str(configured));
if (Kernel()) {
// A series' code unit is keyed on the code (the push gates'
// rule, 2026-10-06.6); every text unit on its text and what it
// cites: the [n] URLs, the anchor when it is dated, the R-units
// it names.
if (a.mode == "series" && Dyn::Get(job, "unitKind") == "code") return Plan::CodeReuseKey(job, lensFile, text, RangeCode(), ids, charterSha.at(lensFile), PromptVersion, env);
return Plan::ReuseKey(job, lensFile, text, Plan::TextDeps(text, units, lines, refUrls, anchorText), ids, charterSha.at(lensFile), PromptVersion);
}
return Plan::BlockReuseKey(job, lensFile, text, RangeCode(), ids, charterSha.at(lensFile), PromptVersion, env);
}
@ -1171,6 +1265,7 @@ namespace Crafter::Review::Run {
// The block's keys in every other environment: a defect its pass
// returns here drops the clean verdicts those hold for the same code -
// a finding is not undone by switching the lens's environment.
if (Kernel()) return {};
std::set<std::string> out;
for (const Plan::Environment& e : Plan::Environments) {
const std::optional<std::string> k = ReuseKey(job, std::string(e.name));
@ -1295,6 +1390,7 @@ namespace Crafter::Review::Run {
auto stamp = [&](Json& rec) {
rec["lens"] = job.at("lens");
rec["lensFile"] = job.at("lensFile");
rec["persona"] = Dyn::Get(job, "persona");
rec["kind"] = job.at("kind");
rec["unit"] = Dyn::Get(job, "unit");
rec["unitSha"] = Dyn::Get(job, "unitSha");
@ -1319,12 +1415,12 @@ namespace Crafter::Review::Run {
const std::lock_guard guard(shared);
if (sched) sched->Abort(reason);
};
const std::string now = Artifact::ArtifactHash("commits", a.artifact);
const std::string now = Artifact::ArtifactHash(a.mode, a.artifact);
if (now != hash) {
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 = a.mode == "commits" ? treeDrift() : std::nullopt) {
abortPool(std::format("evidence drift during the run ({})", *drift));
return {{{"label", job.at("label")}, {"status", "error"}, {"reason", std::format("{} - aborted", *drift)}}, nullptr};
}
@ -1380,23 +1476,33 @@ namespace Crafter::Review::Run {
// ---- a gate -----------------------------------------------------------------------------
std::vector<Json> Run::RunGate(std::string_view title, std::vector<std::string> lensFiles) {
// lensFiles -> the merged lens results, one per lens.
std::vector<std::pair<std::string, std::string>> specs;
for (const std::string& lf : lensFiles) specs.emplace_back(lf, std::string());
return RunGate(title, std::move(specs));
}
std::vector<Json> Run::RunGate(std::string_view title, std::vector<std::pair<std::string, std::string>> specs) {
// specs -> the merged lens results, one per lens instance.
std::vector<std::string> names;
for (const std::string& lf : lensFiles) names.push_back(Plan::LensName(lf));
for (const auto& [lf, persona] : specs) names.push_back(Plan::LensName(lf, persona));
log(std::format("{}: {}", title, Dyn::Join(names, ", ")));
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)) {
std::vector<std::pair<std::string, std::string>> live;
for (const auto& [lf, persona] : specs) {
if (std::optional<Json> r = resumable(Plan::LensName(lf, persona), lf, title)) {
resumed.push_back(std::move(*r));
} else {
live.push_back(lf);
live.emplace_back(lf, persona);
}
}
lensFiles = live;
specs = live;
// Nothing runs: no stack, database or application is needed for a
// result already bought.
if (lensFiles.empty()) return resumed;
if (specs.empty()) return resumed;
std::vector<std::string> lensFiles;
for (const auto& [lf, persona] : specs) {
if (!std::ranges::contains(lensFiles, lf)) lensFiles.push_back(lf);
}
instrumentPreflight(lensFiles);
std::map<std::string, std::vector<Json>> parts;
std::map<std::string, std::set<std::string>> missing;
@ -1406,12 +1512,12 @@ namespace Crafter::Review::Run {
const std::vector<std::string> exclude = Strings(cfg.at("functional").at("exclude"));
std::vector<std::string> unitFilter;
if (a.units) unitFilter.assign(a.units->begin(), a.units->end());
for (const std::string& lf : lensFiles) {
const std::string name = Plan::LensName(lf);
for (const auto& [lf, persona] : specs) {
const std::string name = Plan::LensName(lf, persona);
parts[name];
missing[name];
planned[name];
Plan::LensPlan plan = Plan::PlanLens(lf, units, lines, unitFilter, exclude);
Plan::LensPlan plan = Plan::PlanLens(lf, units, lines, unitFilter, exclude, persona);
if (!plan.whole.is_null()) {
plan.whole["gate"] = title;
for (const Json& id : plan.whole.at("expectedIds")) planned[name].insert(Dyn::Str(id));
@ -1457,9 +1563,38 @@ namespace Crafter::Review::Run {
}
{
const std::lock_guard guard(shared);
summaries[name] = Dyn::Slice(Dyn::StrOr(Dyn::Get(part, "summary"), ""), 0, 2500);
// A persona's digest is its summary, else the review it
// would send.
std::string digest = Dyn::StrOr(Dyn::Get(part, "summary"), "");
if (digest.empty()) digest = Dyn::StrOr(Dyn::Get(part, "predictedReview"), "");
summaries[name] = Dyn::Slice(digest, 0, 2500);
}
return PrepareBlocks(name, pendingBlocks[name], parts);
std::vector<Json> blocks = pendingBlocks[name];
if (Dyn::Truthy(Dyn::Get(job, "persona")) && a.personaBlocks != "all") {
// The units the persona's whole pass marked not-mine get no
// block pass from it: each becomes a pruned part with that
// verdict.
std::vector<std::string> notMine;
for (const Json& id : Dyn::Or(Dyn::Get(part, "notMine"), Dyn::EmptyList())) notMine.push_back(Dyn::Str(id));
std::ranges::sort(notMine);
notMine.erase(std::ranges::unique(notMine).begin(), notMine.end());
if (!notMine.empty()) {
const double ratio = static_cast<double>(notMine.size()) / static_cast<double>(std::max<std::size_t>(1, blocks.size()));
log(std::format("{}: {} units marked not-mine by the whole pass ({:.0f}%){}; those block passes are pruned", name, notMine.size(), ratio * 100,
ratio > 0.5 ? " - WARNING above 50%" : ""));
for (const std::string& uid : notMine) {
parts[name].push_back({{"partLabel", std::format("{}:not-mine:{}", name, uid)}, {"partKind", "pruned"}, {"partUnit", uid}, {"verdict", "SEND-READY"}, {"findings", Json::array()},
{"notes", Json::array()}, {"coverage", Json::array({{{"item", uid}, {"verdict", "not-mine"}, {"basis", "whole pass: not this reviewer's tree, file or decision"}}})},
{"coverageMissing", Json::array()}, {"reused", nullptr}});
}
std::vector<Json> kept;
for (const Json& b : blocks) {
if (!std::ranges::contains(notMine, Dyn::Str(b.at("unit")))) kept.push_back(b);
}
blocks = kept;
}
}
return PrepareBlocks(name, blocks, parts);
};
const bool warm = !a.dryRun && (!testMode || a.warm);
std::unique_ptr<Pool::Admission> admission = Admission(lensFiles);
@ -1489,8 +1624,8 @@ namespace Crafter::Review::Run {
}
if (const std::optional<std::string> aborted = scheduler.Aborted()) abort = aborted;
std::vector<Json> out;
for (const std::string& lf : lensFiles) {
const std::string name = Plan::LensName(lf);
for (const auto& [lf, persona] : specs) {
const std::string name = Plan::LensName(lf, persona);
const bool stoppedEarly = std::ranges::any_of(parts[name], [](const Json& p) { return Dyn::Get(p, "partKind") == "skipped"; });
std::vector<Json> real;
for (const Json& p : parts[name]) {
@ -1699,7 +1834,13 @@ namespace Crafter::Review::Run {
for (const std::string& c : contextPaths) {
if (!fs::exists(c)) Die(std::format("--context-paths: {} does not exist", c));
}
reviewers = CommaList(a.reviewers);
if (!reviewers.empty() && std::ranges::contains(off, std::string(Plan::PersonaLens))) {
Die(std::format("--reviewers names {} but the config turns off the persona lens (lenses.{}.run = false): turn it on, or drop --reviewers", Dyn::Join(reviewers, ", "),
Plan::LensName(Plan::PersonaLens)));
}
Gate0();
const std::vector<std::string> modeLenses = Plan::ModeLenses(a.mode);
std::vector<std::string> only;
if (a.only && !a.only->empty()) {
only = CommaList(*a.only);
@ -1708,6 +1849,11 @@ namespace Crafter::Review::Run {
if (!Plan::IsLens(lf)) bad.push_back(lf);
}
if (!bad.empty()) Die(std::format("--only names unknown lens file(s): {}", Dyn::Join(bad, ", ")));
std::vector<std::string> foreign;
for (const std::string& lf : only) {
if (!std::ranges::contains(modeLenses, lf)) foreign.push_back(lf);
}
if (!foreign.empty()) Die(std::format("--only names lens file(s) the {} mode never runs (nothing would run): {}", a.mode, Dyn::Join(foreign, ", ")));
std::vector<std::string> isOff;
for (const std::string& lf : only) {
if (std::ranges::contains(off, lf)) isOff.push_back(lf);
@ -1724,16 +1870,25 @@ namespace Crafter::Review::Run {
if (!Plan::IsLens(lf)) bad.push_back(lf);
}
if (!bad.empty()) Die(std::format("--without names unknown lens file(s): {}", Dyn::Join(bad, ", ")));
std::vector<std::string> foreign;
for (const std::string& lf : without) {
if (!std::ranges::contains(modeLenses, lf)) foreign.push_back(lf);
}
if (!foreign.empty()) Die(std::format("--without names lens file(s) the {} mode never runs: {}", a.mode, Dyn::Join(foreign, ", ")));
log(std::format("without: {} - left out of this run by owner ruling (recorded in the dump's args); the run stays a full run", Dyn::Join(without, ", ")));
}
const std::vector<Plan::Gate> gates = Plan::GatesFor(only, without, off);
const std::vector<Plan::Gate> gates = Plan::GatesFor(a.mode, only, without, off);
if (gates.empty()) {
std::vector<std::string> why;
if (off.size() == Plan::Lenses.size()) {
std::vector<std::string> offHere;
for (const std::string& lf : off) {
if (std::ranges::contains(modeLenses, lf)) offHere.push_back(lf);
}
if (offHere.size() == modeLenses.size()) {
why.emplace_back("the config turns on no lens (every lens is off until lenses.<name>.run = true turns it on)");
} else if (!off.empty()) {
} else if (!offHere.empty()) {
std::vector<std::string> names;
for (const std::string& lf : off) names.push_back(Plan::LensName(lf));
for (const std::string& lf : offHere) names.push_back(Plan::LensName(lf));
why.push_back(std::format("the config turns off {}", Dyn::Join(names, ", ")));
}
if (!without.empty()) why.push_back(std::format("--without drops {}", Dyn::Join(without, ", ")));
@ -1771,6 +1926,32 @@ namespace Crafter::Review::Run {
break;
}
}
personaRan = false;
const std::string personaFile(Plan::PersonaLens);
if (!Dyn::Truthy(stoppedAt) && !reviewers.empty() && (only.empty() || std::ranges::contains(only, personaFile)) && !std::ranges::contains(without, personaFile)) {
// The persona gate: one instance of the persona lens per
// reviewer, after every other gate passed.
std::vector<std::pair<std::string, std::string>> specs;
for (const std::string& r : reviewers) specs.emplace_back(personaFile, r);
const std::vector<Json> res = RunGate(Plan::PersonaGate, specs);
std::ranges::copy(res, std::back_inserter(results));
Checkpoint(Plan::PersonaGate);
std::vector<std::string> notReturned;
for (const Json& r : res) {
if (Dyn::Truthy(r.at("coverageMissing"))) notReturned.push_back(Dyn::Str(r.at("lens")));
}
if (abort) {
stoppedAt = std::format("{} - ABORTED ({})", Plan::PersonaGate, *abort);
incomplete.push_back({{"gate", Plan::PersonaGate}, {"reason", *abort}});
} else if (!notReturned.empty()) {
stoppedAt = std::format("{} - INCOMPLETE ({} not returned)", Plan::PersonaGate, Dyn::Join(notReturned, ", "));
incomplete.push_back({{"gate", Plan::PersonaGate}, {"notReturned", Array(notReturned)}});
} else if (std::ranges::any_of(res, [](const Json& r) { return r.at("verdict") == "FIX FIRST"; })) {
stoppedAt = Plan::PersonaGate;
} else {
personaRan = true;
}
}
const bool anyFailed = std::ranges::any_of(results, [](const Json& r) { return r.at("verdict") == "FIX FIRST"; });
if (x > 1 && !a.dryRun && !testMode) {
// The file after this run's store: a --no-reuse run counts its own
@ -1814,16 +1995,19 @@ namespace Crafter::Review::Run {
owedReads += o.at("reads").get<std::int64_t>();
}
overall = std::format("CONFIRMING - NOT SEND-READY (gates clean; {} code unit(s) owe {} clean read(s) to reach {}: run the same line again)", owedUnits, owedReads, x);
} else {
// Gates 1-3 clean = push-ready (Gate 4 is the owner).
} else if (personaRan || a.mode == "commits") {
// commits: no persona gate - the gates clean = push-ready (the
// owner is the last gate).
overall = "SEND-READY";
} else {
overall = "GATES CLEAN - NOT SEND-READY (no reviewers passed, Gate 4 did not run)";
}
return Finish(overall, stoppedAt, incomplete);
}
std::int32_t Run::Finish(std::string overall, const Json& stoppedAt, Json incomplete) {
if (broker) broker->Close();
if (Artifact::ArtifactHash("commits", a.artifact) != hash) {
if (Artifact::ArtifactHash(a.mode, a.artifact) != hash) {
overall = "INCOMPLETE - NOT SEND-READY (artifact changed during the run)";
incomplete.push_back({{"gate", "end"}, {"reason", "artifact hash changed during the run"}});
}
@ -1924,13 +2108,29 @@ namespace Crafter::Review::Run {
const Json& db = Dyn::Get(s, "dbUrl");
stackList.push_back({{"id", s.at("id")}, {"appUrl", s.at("appUrl")}, {"dbUrl", Dyn::Truthy(db) ? Json(PgWire::Redact(Dyn::Str(db))) : Json()}, {"login", Dyn::Get(s, "login")}});
}
const std::vector<std::string> modeLenses = Plan::ModeLenses(a.mode);
Json lensesOff = Json::array();
for (const std::string& lf : off) lensesOff.push_back(Plan::LensName(lf));
for (const std::string& lf : off) {
if (std::ranges::contains(modeLenses, lf)) lensesOff.push_back(Plan::LensName(lf));
}
Json environmentNames = Json::object();
for (auto it = environments.begin(); it != environments.end(); ++it) environmentNames[Plan::LensName(it.key())] = it.value();
for (auto it = environments.begin(); it != environments.end(); ++it) {
if (std::ranges::contains(modeLenses, it.key())) environmentNames[Plan::LensName(it.key())] = it.value();
}
Json refs;
if (Kernel()) {
std::int64_t fetched = 0;
Json failed = Json::array();
for (auto it = refIndex.begin(); it != refIndex.end(); ++it) {
const Json& status = Dyn::Get(it.value(), "status");
if (status == "cached" || status == "fetched") ++fetched;
if (status == "failed") failed.push_back(it.key());
}
refs = {{"index", std::format("gate-state/refs-{}.json", h8)}, {"fetched", fetched}, {"failed", failed}};
}
Json dump = {
{"driver", DriverLine()},
{"mode", "commits"},
{"mode", a.mode},
{"artifact", Dyn::RelPath(a.artifact, repo.string())},
{"hash", hash},
{"date", a.date},
@ -1950,6 +2150,7 @@ namespace Crafter::Review::Run {
{"results", results},
{"processes", recs},
{"usage", {{"run", State::UsageTotals(recs)}, {"perLens", perLensUsage}, {"prefixHitRate", firsts ? Json(Dyn::Round(static_cast<double>(hits) / static_cast<double>(firsts), 2)) : Json()}}},
{"refs", refs},
{"estimate", runJobs.empty() ? Json() : Plan::Estimate(runJobs, static_cast<std::int64_t>(Dyn::Length(runLayer)), lensLayersBytes)},
{"wallMin", (Platform::Now() - t0) / 60},
{"scratch", scratch.string()},
@ -2166,14 +2367,18 @@ namespace Crafter::Review::Run {
}
sets[key] = {{"lens", rec.at("lensFile")}, {"unit", rec.at("unit")}, {"unitSha", Dyn::Get(rec, "unitSha")}, {"hash", hash}, {"run", Basename(dumpPath)}, {"date", a.date},
{"coverage", cov ? *cov : Json()}, {"findings", citing}, {"notes", Normalize::Dedupe(notes)}};
if (Dyn::Truthy(Dyn::Get(rec, "persona"))) sets[key]["persona"] = rec.at("persona");
++n;
}
// One clean read per round: the count comes from the FILE at write
// time, so a read another driver stored is kept; a second store of the
// same run, or the relaunch of an unfinished run, adds nothing.
const Json read = {{"hash", h8}, {"round", round}, {"run", Basename(dumpPath)}};
// One clean read per round, not for a reply: the count comes from the
// FILE at write time, so a read another driver stored is kept; a
// second store of the same run, or the relaunch of an unfinished run,
// adds nothing. The persona pass is recorded once the persona gate
// passed.
const Json read = a.mode == "reply" ? Json() : Json({{"hash", h8}, {"round", round}, {"run", Basename(dumpPath)}});
const Json persona = personaRan ? Json({{"hash", hash}, {"reviewers", Array(reviewers)}, {"date", a.date}}) : Json();
const std::vector<std::string> drops(dirty.begin(), dirty.end());
Json merged = State::SaveState(statePath, sets, drops, read);
Json merged = State::SaveState(statePath, sets, drops, read, persona);
// A --no-reuse run keeps its empty view for the reuse decisions; its
// reads are counted from the file.
if (!a.noReuse) state = merged;