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:
parent
4fc15fe1c2
commit
339f4b9b40
46 changed files with 5732 additions and 362 deletions
|
|
@ -116,7 +116,10 @@ int main() {
|
|||
const std::vector<std::string> onlyFn = {Fn};
|
||||
const std::vector<std::string> security = {"repo-security.md"};
|
||||
std::set<std::string> lensSet;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) lensSet.emplace(lens.file);
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
// The push gates' lenses; the kernel modes' are another mode's.
|
||||
if (!lens.kernel) lensSet.emplace(lens.file);
|
||||
}
|
||||
using GateList = std::vector<std::pair<std::string, std::vector<std::string>>>;
|
||||
Check("mode_lenses = the five; --only keeps, --without drops, an emptied gate disappears; Gate 3 with one of its lenses dropped keeps the other",
|
||||
Set(Plan::ModeLenses()) == lensSet && Gates(Plan::GatesFor(onlyFn)) == GateList{{"Gate 2 — Functional", {Fn}}}
|
||||
|
|
@ -135,12 +138,14 @@ int main() {
|
|||
const std::set<std::string> nrAll = Set(Plan::NotReviewed(units, lines, Plan::ModeLenses()));
|
||||
std::vector<std::string> withoutFacts;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
if (lens.kernel) continue;
|
||||
if (lens.file != Facts) withoutFacts.emplace_back(lens.file);
|
||||
}
|
||||
Check("not reviewed (every lens runs): only the documentation file's header, a mechanical n/a of the commit-truth lens; without that lens the text units and the documentation units",
|
||||
nrAll == std::set<std::string>{"p2:README.md#hdr"} && Set(Plan::NotReviewed(units, lines, withoutFacts)) == textAndDoc, Show(nrAll));
|
||||
std::vector<std::string> statics;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
if (lens.kernel) continue;
|
||||
if (lens.file != Fn) statics.emplace_back(lens.file);
|
||||
}
|
||||
std::set<std::string> plusBehaviour = nrAll;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,11 @@ int main() {
|
|||
const Json defaults = Config::Defaults();
|
||||
std::set<std::string> defaultNames;
|
||||
for (auto it = defaults.at("lenses").begin(); it != defaults.at("lenses").end(); ++it) defaultNames.insert(it.key());
|
||||
const Json environments = {{"repo-better-solution.md", "none"}, {fn, "browser"}, {"repo-bug-prover.md", "none"}, {"repo-security.md", "none"}, {Plan::FactsLens, "none"}};
|
||||
Json environments = {{"repo-better-solution.md", "none"}, {fn, "browser"}, {"repo-bug-prover.md", "none"}, {"repo-security.md", "none"}, {Plan::FactsLens, "none"}};
|
||||
// The kernel modes' lenses (2026-10-31), every one without an environment.
|
||||
for (const std::string_view lf : {"better-solution.md", "code-quality.md", "minimalism.md", "bug-prover.md", "facts.md", "consistency.md", "process-docs.md", "etiquette.md", "conciseness.md", "maintainer-persona.md"}) {
|
||||
environments[std::string(lf)] = "none";
|
||||
}
|
||||
Check("lenses: every lens is off by default (2026-10-31, opt-in only) with plan.LENSES' env - none/browser/none/none/none - and a config that turns none on has the one problem that says so; the table names every lens of plan.LENSES",
|
||||
noLensOn(loaded.problems) && noneRun && Config::LensesOff(loaded.config) == everyLens && Dyn::Equal(Config::Environments(loaded.config), environments) && defaultNames == names && defaultsMatch,
|
||||
Show(loaded.problems));
|
||||
|
|
|
|||
|
|
@ -22,7 +22,11 @@ import sys, json, hashlib, subprocess, pathlib, tempfile, os, shutil
|
|||
W = pathlib.Path(sys.argv[1]); EXE = sys.argv[2]
|
||||
G = W / 'tests/Goldens/fixture/pipeline.json'
|
||||
g = json.loads(G.read_text())
|
||||
TABLE = [('repo-better-solution.md', 'none'), ('repo-functional.md', 'browser'), ('repo-bug-prover.md', 'none'), ('repo-security.md', 'none'), ('repo-facts.md', 'none')]
|
||||
TABLE = [('repo-better-solution.md', 'none'), ('repo-functional.md', 'browser'), ('repo-bug-prover.md', 'none'), ('repo-security.md', 'none'), ('repo-facts.md', 'none'),
|
||||
('better-solution.md', 'none'), ('code-quality.md', 'none'), ('minimalism.md', 'none'), ('bug-prover.md', 'none'), ('facts.md', 'none'), ('consistency.md', 'none'),
|
||||
('process-docs.md', 'none'), ('etiquette.md', 'none'), ('conciseness.md', 'none'), ('maintainer-persona.md', 'none')]
|
||||
# The push gates' lenses: the plans and estimates of the series goldens are theirs.
|
||||
REPO_TABLE = TABLE[:5]
|
||||
GATES = [('Gate 1 — Approach', ['repo-better-solution.md']), ('Gate 2 — Functional', ['repo-functional.md']),
|
||||
('Gate 3 — Proof', ['repo-bug-prover.md', 'repo-security.md']), ('Gate 4 — Commit truth', ['repo-facts.md'])]
|
||||
name_of = lambda f: f.replace('.md', '')
|
||||
|
|
@ -129,7 +133,7 @@ for name, entry in g['series'].items():
|
|||
if 'plans' not in entry: continue
|
||||
jobs = []
|
||||
recorded = []
|
||||
for f, _ in TABLE:
|
||||
for f, _ in REPO_TABLE:
|
||||
plan = entry['plans'].get(f)
|
||||
if plan is None: continue
|
||||
here = ([plan['whole']] if plan['whole'] is not None else []) + plan['blocks']
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -94,6 +94,8 @@ namespace {
|
|||
std::map<std::string, Json> wholeByLens;
|
||||
const std::vector<std::string> exclude = {"**/*.test.*", "docs/**", "tests/**"};
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
// The goldens record the push gates; the kernel modes' lenses are another mode's.
|
||||
if (lens.kernel) continue;
|
||||
const Plan::LensPlan plan = Plan::PlanLens(lens.file, units, built.lines, {}, exclude);
|
||||
Json blocks = Json::array();
|
||||
for (const Json& block : plan.blocks) blocks.push_back(block);
|
||||
|
|
@ -141,6 +143,8 @@ namespace {
|
|||
std::vector<std::string> all;
|
||||
std::vector<std::string> statics;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
// The goldens record the push gates; the kernel modes' lenses are another mode's.
|
||||
if (lens.kernel) continue;
|
||||
all.emplace_back(lens.file);
|
||||
if (lens.file != Plan::FunctionalLens) statics.emplace_back(lens.file);
|
||||
}
|
||||
|
|
@ -150,6 +154,8 @@ namespace {
|
|||
SameJson(ToJson(Plan::NotReviewed(units, built.lines, functional, exclude)), golden.at("notReviewed").at("functional"), std::format("{}: not_reviewed functional", name));
|
||||
std::vector<Json> jobs;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
// The goldens record the push gates; the kernel modes' lenses are another mode's.
|
||||
if (lens.kernel) continue;
|
||||
for (const bool excluded : {false, true}) {
|
||||
const Plan::LensPlan plan = Plan::PlanLens(lens.file, units, built.lines, {}, excluded ? std::span<const std::string>(exclude) : std::span<const std::string>());
|
||||
Json blocks = Json::array();
|
||||
|
|
|
|||
44
tests/KernelModes/fixture/draft-camera.txt
Normal file
44
tests/KernelModes/fixture/draft-camera.txt
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
Hallo Thomas,
|
||||
|
||||
Dank je voor de nieuwe versie.
|
||||
|
||||
ov13b10-of/0001-media-i2c-ov13b10.patch
|
||||
|
||||
De eerste patch ziet er goed uit; zie punt 1 hieronder.
|
||||
|
||||
ov13b10-of/0000-cover-letter.patch
|
||||
|
||||
De cover noemt de test op 2026-09-10; dat klopt met wat ik zag.
|
||||
|
||||
Dates: every "still", "unmerged", "in review" and "latest version" below is
|
||||
as of 2026-09-21 (work/linux at v7.1-rc2, work/milos-linux at v7.1.2-milos).
|
||||
|
||||
Reversals of the 2026-08-18 review (two points where that review was wrong):
|
||||
|
||||
R1. The active-array origin (ov13b10 3/3). The 08-18 review said the origin
|
||||
was (0, 0); the datasheet [1] gives (8, 8), so the earlier ask is withdrawn.
|
||||
|
||||
R2. The link-frequencies constraint (ov13b10 1/3) depends on R1 and on the
|
||||
binding example in [2]; it is still open as of 2026-09-21.
|
||||
|
||||
camss-milos
|
||||
|
||||
1. num_clients (camss 2/5). The field is read once; the v3 series on the
|
||||
list [1] does the same.
|
||||
a. The error path leaves the clock enabled.
|
||||
b. The comment above it names the wrong register.
|
||||
|
||||
2. Required supplies (camss 1/5). Matches the binding in [2].
|
||||
|
||||
ov13b10-of
|
||||
|
||||
1. The crop default. A plain paragraph item without a citation at all.
|
||||
|
||||
across the three series
|
||||
|
||||
- Every series cites the same datasheet; the three cover letters should
|
||||
agree on its revision.
|
||||
- The Cc lists differ between the series.
|
||||
|
||||
[1] https://lore.kernel.org/r/7927bdab-9c3b-460d-a2ba-2e5b06ee8804@kernel.org/
|
||||
[2] https://lore.kernel.org/all/20260815.4567@example.org/
|
||||
22
tests/KernelModes/fixture/reply.eml
Normal file
22
tests/KernelModes/fixture/reply.eml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
To: Some Maintainer <maintainer@example.org>
|
||||
Cc: linux-example@vger.kernel.org
|
||||
Subject: Re: [PATCH v2] nfc: widget: add the driver
|
||||
In-Reply-To: <20260920.111@example.org>
|
||||
References: <20260920.111@example.org>
|
||||
|
||||
On Sat, 20 Sep 2026 at 10:00, Some Maintainer wrote:
|
||||
> The probe should not read the revision twice.
|
||||
>
|
||||
> Please drop the second read.
|
||||
|
||||
Agreed, v3 reads it once and keeps it for the attribute.
|
||||
|
||||
> And the binding needs the example.
|
||||
|
||||
The example is in 2/2 of v3; it follows [1].
|
||||
|
||||
[1] https://lore.kernel.org/all/20260815.4567@example.org/
|
||||
|
||||
Tested-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Reviewed-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
25
tests/KernelModes/fixture/series/0000-cover-letter.patch
Normal file
25
tests/KernelModes/fixture/series/0000-cover-letter.patch
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
From 3333333333333333333333333333333333333333 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Thu, 25 Sep 2026 09:00:00 +0200
|
||||
Subject: [PATCH 0/2] nfc: widget: add the driver and its binding
|
||||
|
||||
This series adds the widget driver and documents its binding.
|
||||
|
||||
The driver was tested on the board described in [1]; the binding follows
|
||||
the example the maintainer asked for in the v1 review, which is still
|
||||
unmerged as of 2026-09-21.
|
||||
|
||||
[1] https://lore.kernel.org/all/20260901.1234@example.org/
|
||||
|
||||
Jorijn van der Graaf (2):
|
||||
nfc: widget: add the driver
|
||||
dt-bindings: nfc: document the widget
|
||||
|
||||
Documentation/devicetree/bindings/nfc/widget.yaml | 18 ++++++++++++++++++
|
||||
README.md | 3 +++
|
||||
drivers/nfc/widget.c | 12 ++++++++++++
|
||||
3 files changed, 33 insertions(+)
|
||||
|
||||
base-commit: 0000000000000000000000000000000000000000
|
||||
--
|
||||
2.51.0
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
From 4444444444444444444444444444444444444444 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Thu, 25 Sep 2026 09:01:00 +0200
|
||||
Subject: [PATCH 1/2] nfc: widget: add the driver
|
||||
|
||||
The widget answers on I2C and reports its revision at probe. The probe
|
||||
reads the revision register once and keeps it for the sysfs attribute.
|
||||
|
||||
Tested on the board as of 2026-09-20 with v1 of the firmware.
|
||||
|
||||
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Reviewed-by: Some Reviewer <reviewer@example.org>
|
||||
---
|
||||
drivers/nfc/widget.c | 12 ++++++++++++
|
||||
1 file changed, 12 insertions(+)
|
||||
create mode 100644 drivers/nfc/widget.c
|
||||
|
||||
diff --git a/drivers/nfc/widget.c b/drivers/nfc/widget.c
|
||||
new file mode 100644
|
||||
index 0000000..a1b2c3d
|
||||
--- /dev/null
|
||||
+++ b/drivers/nfc/widget.c
|
||||
@@ -0,0 +1,12 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0
|
||||
+#include <linux/i2c.h>
|
||||
+
|
||||
+static int widget_probe(struct i2c_client *client)
|
||||
+{
|
||||
+ int rev;
|
||||
+
|
||||
+ rev = i2c_smbus_read_byte_data(client, 0x00);
|
||||
+ if (rev < 0)
|
||||
+ return rev;
|
||||
+ return 0;
|
||||
+}
|
||||
--
|
||||
2.51.0
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
From 5555555555555555555555555555555555555555 Mon Sep 17 00:00:00 2001
|
||||
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
Date: Thu, 25 Sep 2026 09:02:00 +0200
|
||||
Subject: [PATCH 2/2] dt-bindings: nfc: document the widget
|
||||
|
||||
Document the widget's compatible and its one supply; the README gains a
|
||||
line about the binding.
|
||||
|
||||
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
|
||||
---
|
||||
Documentation/devicetree/bindings/nfc/widget.yaml | 18 ++++++++++++++++++
|
||||
README.md | 3 +++
|
||||
2 files changed, 21 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/nfc/widget.yaml
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/nfc/widget.yaml b/Documentation/devicetree/bindings/nfc/widget.yaml
|
||||
new file mode 100644
|
||||
index 0000000..c3d4e5f
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/nfc/widget.yaml
|
||||
@@ -0,0 +1,18 @@
|
||||
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
+%YAML 1.2
|
||||
+---
|
||||
+$id: http://devicetree.org/schemas/nfc/widget.yaml#
|
||||
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
+
|
||||
+title: Widget NFC controller
|
||||
+
|
||||
+properties:
|
||||
+ compatible:
|
||||
+ const: example,widget
|
||||
+ vdd-supply: true
|
||||
+
|
||||
+required:
|
||||
+ - compatible
|
||||
+ - vdd-supply
|
||||
+
|
||||
+additionalProperties: false
|
||||
diff --git a/README.md b/README.md
|
||||
index d4e5f6a..e5f6a7b 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -1,2 +1,5 @@
|
||||
# hello
|
||||
|
||||
+The widget binding lives under Documentation/devicetree/bindings/nfc/.
|
||||
+It names one supply.
|
||||
+
|
||||
--
|
||||
2.51.0
|
||||
714
tests/KernelModes/main.cpp
Normal file
714
tests/KernelModes/main.cpp
Normal file
|
|
@ -0,0 +1,714 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// The kernel modes (reply, series): the inventory of a review draft, a plain
|
||||
// mail and a series with a cover letter; the gates, efforts, reads and
|
||||
// budgets per mode; the plans (every slot, the bare headers, a persona
|
||||
// instance); the text and series reuse keys; the dispositions' carry and
|
||||
// block texts; the prompts (common text, run layer, tails, lens layer); the
|
||||
// kernel schema; the persona vocabulary in normalize and the matrix; the
|
||||
// references; the state's persona pass; the allowlist; and dry runs of both
|
||||
// modes through this binary, the persona gate included.
|
||||
|
||||
import std;
|
||||
import Crafter.Review;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter::Review;
|
||||
|
||||
namespace {
|
||||
std::int32_t Failures = 0;
|
||||
|
||||
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::vector<std::string> Ids(const Json& units) {
|
||||
std::vector<std::string> out;
|
||||
for (const Json& u : units) out.push_back(Dyn::Str(u.at("id")));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Show(const std::vector<std::string>& values) {
|
||||
Json out = Json::array();
|
||||
for (const std::string& v : values) out.push_back(v);
|
||||
return Dyn::ReprValue(out);
|
||||
}
|
||||
|
||||
const Json& Unit(const Json& units, std::string_view id) {
|
||||
for (const Json& unit : units) {
|
||||
if (unit.at("id") == id) return unit;
|
||||
}
|
||||
throw std::runtime_error(std::format("no unit {}", id));
|
||||
}
|
||||
|
||||
std::vector<std::string> Titles(const std::vector<Plan::Gate>& gates) {
|
||||
std::vector<std::string> out;
|
||||
for (const Plan::Gate& gate : gates) out.emplace_back(gate.title);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::string> LensesOf(const Plan::Gate& gate) {
|
||||
return {gate.lenses.begin(), gate.lenses.end()};
|
||||
}
|
||||
|
||||
std::map<std::string, std::int64_t> Kinds(const std::vector<Json>& jobs) {
|
||||
std::map<std::string, std::int64_t> out;
|
||||
for (const Json& j : jobs) ++out[Dyn::Str(j.at("kind"))];
|
||||
return out;
|
||||
}
|
||||
|
||||
std::set<std::string> Units(const std::vector<Json>& jobs, std::string_view kind = {}) {
|
||||
std::set<std::string> out;
|
||||
for (const Json& j : jobs) {
|
||||
if (kind.empty() || j.at("kind") == kind) out.insert(Dyn::Str(j.at("unit")));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// claude for the dry runs' sibling full run: the units from its prompt,
|
||||
// every unit clear; a persona's whole pass marks nothing not-mine.
|
||||
constexpr std::string_view FakeClaude = R"sh(#!/bin/sh
|
||||
set -f
|
||||
[ "$1" = --version ] && { echo '2.1.314 (Claude Code)'; exit 0; }
|
||||
model=
|
||||
sid=
|
||||
prev=
|
||||
for a in "$@"; do
|
||||
case $prev in
|
||||
--model) model=$a ;;
|
||||
--session-id) sid=$a ;;
|
||||
esac
|
||||
prev=$a
|
||||
done
|
||||
ids=$(sed -n -e 's/^SCOPE - BLOCK PASS, unit "\([^"]*\)".*/\1/p' -e 's/^SCOPE - SLOT PASS, unit "\([^"]*\)".*/\1/p' -e 's/^- \(whole:[a-z-]*\) \[.*/\1/p')
|
||||
echo "$$" $ids >> "$FAKE_DIR/calls"
|
||||
echo "{\"type\":\"system\",\"subtype\":\"init\",\"session_id\":\"$sid\",\"model\":\"$model\",\"mcp_servers\":[],\"tools\":[\"Read\"]}"
|
||||
echo '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"reading"}]}}'
|
||||
coverage=
|
||||
summary=
|
||||
for id in $ids; do
|
||||
coverage="$coverage${coverage:+,}{\"item\":\"$id\",\"verdict\":\"clear\",\"basis\":\"no record\"}"
|
||||
case $id in whole:*) summary=',"summary":"the approach holds","predictedReview":"Looks fine.","notMine":[]' ;; esac
|
||||
done
|
||||
echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\":\"ok\",\"structured_output\":{\"verdict\":\"SEND-READY\",\"findings\":[],\"notes\":[],\"coverage\":[$coverage]$summary},\"usage\":{\"input_tokens\":1,\"output_tokens\":1,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0},\"total_cost_usd\":0.01,\"num_turns\":1}"
|
||||
)sh";
|
||||
|
||||
std::map<std::string, std::string> Env;
|
||||
|
||||
std::map<std::string, std::string> With(std::map<std::string, std::string> env, const std::map<std::string, std::string>& changes) {
|
||||
for (const auto& [k, v] : changes) env[k] = v;
|
||||
return env;
|
||||
}
|
||||
|
||||
// A reply's or series' reference index as a run leaves it, so a dry run
|
||||
// fetches nothing: every entry failed.
|
||||
void FailedIndex(const fs::path& gateDir, std::string_view h8, const Refs::Table& refs) {
|
||||
Json index = Json::object();
|
||||
for (const auto& [n, url] : refs) {
|
||||
const std::optional<std::string> mid = Refs::MsgidOf(url);
|
||||
index[n] = {{"url", url}, {"msgid", mid.value_or("")}, {"path", std::format("gate-state/refs/{}.raw", Refs::SafeName(mid.value_or("")))}, {"status", "failed"}, {"error", "no lore here"}};
|
||||
}
|
||||
Write(gateDir / std::format("refs-{}.json", h8), DumpFile(index));
|
||||
}
|
||||
|
||||
std::string SystemFiles(const fs::path& dry) {
|
||||
std::string all;
|
||||
std::error_code error;
|
||||
for (fs::directory_iterator it(dry, error), end; !error && it != end; it.increment(error)) {
|
||||
const std::string name = it->path().filename().string();
|
||||
if (name.starts_with("system-") && name.ends_with(".md")) all += Platform::ReadFile(it->path()).value_or("");
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
std::string Prompts(const fs::path& dry) {
|
||||
std::string all;
|
||||
std::error_code error;
|
||||
for (fs::directory_iterator it(dry / "prompts", error), end; !error && it != end; it.increment(error)) {
|
||||
if (it->path().extension() == ".txt") all += Platform::ReadFile(it->path()).value_or("");
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
std::int32_t Checks() {
|
||||
std::println("kernel modes: the inventory, gates, plans, keys, dispositions, prompts, schema, normalize, refs, state, allowlist, dry runs");
|
||||
Platform::SetMode(Platform::Mode::Native);
|
||||
const fs::path checkout = fs::current_path();
|
||||
SetHome(checkout);
|
||||
const fs::path fix = checkout / "tests" / "KernelModes" / "fixture";
|
||||
const fs::path base = fs::temp_directory_path() / std::format("crafter-review-kernel-{}", Platform::PublishedPid());
|
||||
fs::remove_all(base);
|
||||
fs::create_directories(base);
|
||||
for (const auto& [k, v] : Platform::Environment()) {
|
||||
const bool review = k.starts_with("REVIEW_GATES_") || k.starts_with("CRAFTER_REVIEW_") || k == "SEATS_ENV_FILE";
|
||||
const bool seat = k.starts_with("CLAUDE_CODE_OAUTH_TOKEN") || k.starts_with("ANTHROPIC_API_KEY") || k.starts_with("OPENROUTER_KEY") || k.starts_with("FAKE_");
|
||||
if (!review && !seat) Env[k] = v;
|
||||
}
|
||||
Env = With(Env, {{"GIT_CONFIG_GLOBAL", "/dev/null"}, {"GIT_CONFIG_SYSTEM", "/dev/null"}, {"CRAFTER_REVIEW_HOME", checkout.string()}, {"REVIEW_GATES_BOARD", (base / "board.json").string()}});
|
||||
|
||||
// ---- the inventory --------------------------------------------------------------
|
||||
const Inventory::Built draft = Inventory::Build("reply", (fix / "draft-camera.txt").string());
|
||||
const std::vector<std::string> draftIds = Ids(draft.out.at("units"));
|
||||
Check("reply, the camera review's shape: the author's groups, the anchor, the reversals, the sections' items and sub-items, the bullets, the refs, the three slots",
|
||||
draftIds == std::vector<std::string>{"nl:intro", "nl:ov13b10-of/0001-media-i2c-ov13b10.patch", "nl:ov13b10-of/0000-cover-letter.patch", "anchor", "reversals:header", "R1", "R2",
|
||||
"camss:header", "camss:1", "camss:1a", "camss:1b", "camss:2", "ov13b10:header", "ov13b10:1", "x:header", "x:1", "x:2", "refs", "whole:route",
|
||||
"whole:asks", "whole:absent"}
|
||||
&& Dyn::Str(draft.out.at("hash8")) == Artifact::Sha256File(fix / "draft-camera.txt").substr(0, 8) && Dyn::Str(draft.out.at("mode")) == "reply",
|
||||
Show(draftIds));
|
||||
const Json& draftUnits = draft.out.at("units");
|
||||
Check("...a reversal holds its continuation line, a sub-item is one line, the refs run to the end",
|
||||
Plan::UnitText(draft.lines, Unit(draftUnits, "R1")).starts_with("R1. The active-array origin") && Plan::UnitText(draft.lines, Unit(draftUnits, "R1")).contains("withdrawn.")
|
||||
&& Dyn::Str(Unit(draftUnits, "camss:1a").at("excerpt")) == "a. The error path leaves the clock enabled." && Plan::UnitText(draft.lines, Unit(draftUnits, "refs")).ends_with("example.org/"),
|
||||
Plan::UnitText(draft.lines, Unit(draftUnits, "R1")));
|
||||
const Inventory::Built mail = Inventory::Build("reply", (fix / "reply.eml").string());
|
||||
const std::vector<std::string> mailIds = Ids(mail.out.at("units"));
|
||||
Check("reply, a plain mail: the header block, each quoted block with its attribution, our paragraphs, the trailer paragraph, the slots",
|
||||
mailIds == std::vector<std::string>{"hdr", "quote:1", "para:1", "quote:2", "para:2", "para:3", "trailers", "whole:route", "whole:asks", "whole:absent"}
|
||||
&& Unit(mail.out.at("units"), "quote:1").at("lines") == Json({8, 11}) && Unit(mail.out.at("units"), "trailers").at("lines") == Json({21, 22})
|
||||
&& Unit(mail.out.at("units"), "hdr").at("lines") == Json({1, 6}),
|
||||
Show(mailIds));
|
||||
const Inventory::Built series = Inventory::Build("series", (fix / "series").string());
|
||||
const std::vector<std::string> seriesIds = Ids(series.out.at("units"));
|
||||
std::vector<std::string> coverIds;
|
||||
for (const std::string& id : seriesIds) {
|
||||
if (id.starts_with("cover:")) coverIds.push_back(id);
|
||||
}
|
||||
Check("series: the cover letter's header and its six paragraphs (the base-commit line and the signature are one), every patch's units, a .md hunk whole, the four series slots",
|
||||
coverIds == std::vector<std::string>{"cover:header", "cover:1", "cover:2", "cover:3", "cover:4", "cover:5", "cover:6"}
|
||||
&& std::ranges::contains(seriesIds, std::string("p1:drivers/nfc/widget.c#1")) && std::ranges::contains(seriesIds, std::string("p2:README.md#1"))
|
||||
&& !std::ranges::contains(seriesIds, std::string("p2:README.md#1.1")) && std::ranges::contains(seriesIds, std::string("p1:trailers"))
|
||||
&& seriesIds.back() == "whole:absent" && std::ranges::contains(seriesIds, std::string("whole:cover-vs-patches")) && std::ranges::contains(seriesIds, std::string("whole:recipients"))
|
||||
&& Unit(series.out.at("units"), "cover:header").at("lines") == Json({1, 5}),
|
||||
Show(seriesIds));
|
||||
Check("series: a .md hunk is code to the kernel lenses and doc to the push gates' commit-truth lens",
|
||||
Plan::KindFor("bug-prover.md", Unit(series.out.at("units"), "p2:README.md#1")) == "code" && Plan::KindFor("facts.md", Unit(series.out.at("units"), "p2:README.md#1")) == "code"
|
||||
&& Plan::KindFor(Plan::FactsLens, Unit(series.out.at("units"), "p2:README.md#1")) == "doc");
|
||||
|
||||
// ---- gates, effort, reads, budgets ---------------------------------------------
|
||||
Check("gates: reply runs Gate 3 alone; series Gates 1-3; --only and --without filter within the mode; a foreign lens filters to nothing",
|
||||
Titles(Plan::Gates("reply")) == std::vector<std::string>{"Gate 3 — Artifact truth"} && LensesOf(Plan::Gates("reply")[0]) == std::vector<std::string>{"facts.md", "etiquette.md", "consistency.md", "conciseness.md"}
|
||||
&& Titles(Plan::Gates("series")) == std::vector<std::string>{"Gate 1 — Structure", "Gate 2 — Code", "Gate 3 — Artifact truth"}
|
||||
&& LensesOf(Plan::GatesFor("reply", std::vector<std::string>{"facts.md"}, {}, {})[0]) == std::vector<std::string>{"facts.md"}
|
||||
&& Titles(Plan::GatesFor("series", std::vector<std::string>{"bug-prover.md"}, {}, {}))[0].starts_with("Gate 2")
|
||||
&& LensesOf(Plan::GatesFor("reply", {}, std::vector<std::string>{"conciseness.md"}, {})[0]) == std::vector<std::string>{"facts.md", "etiquette.md", "consistency.md"}
|
||||
&& Plan::GatesFor("reply", std::vector<std::string>{"facts.md"}, std::vector<std::string>{"facts.md"}, {}).empty()
|
||||
&& Plan::GatesFor("commits", std::vector<std::string>{"facts.md"}, {}, {}).empty() && Plan::GatesFor().size() == Plan::Gates("commits").size());
|
||||
Check("mode lenses: the kernel modes have the persona lens, commits does not; effort max for the kernel modes, xhigh for commits; reads 1 for a reply, 3 otherwise",
|
||||
std::ranges::contains(Plan::ModeLenses("series"), std::string(Plan::PersonaLens)) && std::ranges::contains(Plan::ModeLenses("reply"), std::string(Plan::PersonaLens))
|
||||
&& !std::ranges::contains(Plan::ModeLenses("commits"), std::string(Plan::PersonaLens)) && Plan::DefaultEffort("reply") == "max" && Plan::DefaultEffort("series") == "max"
|
||||
&& Plan::DefaultEffort("commits") == "xhigh" && Plan::DefaultEffort() == "xhigh" && Plan::ReadsFor("reply") == 1 && Plan::ReadsFor("series") == 3 && Plan::ReadsFor("commits") == 3);
|
||||
auto budget = [](std::string_view kind, std::string_view unitKind, std::string_view lens) {
|
||||
const Plan::Budget b = Plan::BudgetFor(kind, unitKind, lens);
|
||||
return std::pair(b.maxTurns, b.timeoutMin);
|
||||
};
|
||||
Check("budgets: facts text 80/50, other text 50/30, slot 50/35, whole 180/105",
|
||||
budget("block", "text", "facts.md") == std::pair(80, 50) && budget("block", "text", "etiquette.md") == std::pair(50, 30) && budget("slot", "slot", "facts.md") == std::pair(50, 35)
|
||||
&& budget("whole", "", "consistency.md") == std::pair(180, 105) && budget("block", "text", Plan::FactsLens) == std::pair(60, 40));
|
||||
|
||||
// ---- plans ----------------------------------------------------------------------
|
||||
const Plan::LensPlan facts = Plan::PlanLens("facts.md", draftUnits, draft.lines);
|
||||
const std::map<std::string, std::int64_t> factsKinds = Kinds(facts.blocks);
|
||||
Check("facts on the draft: no whole pass; a block per unit - 3 slot passes, 3 bare headers mechanical n/a, the rest blocks",
|
||||
facts.whole.is_null() && factsKinds.at("slot") == 3 && factsKinds.at("auto-na") == 3 && facts.blocks.size() == draftUnits.size() && Units(facts.blocks, "auto-na") == std::set<std::string>{"camss:header", "ov13b10:header", "x:header"},
|
||||
DumpCompact(Json(factsKinds)));
|
||||
const Plan::LensPlan consistency = Plan::PlanLens("consistency.md", draftUnits, draft.lines);
|
||||
std::vector<std::string> wholeSlots;
|
||||
for (const Json& id : consistency.whole.at("expectedIds")) wholeSlots.push_back(Dyn::Str(id));
|
||||
Check("consistency: a whole pass over every whole:* slot, blocks that exclude the slots, no persona key on a plain job",
|
||||
wholeSlots == std::vector<std::string>{"whole:route", "whole:asks", "whole:absent"} && !Units(consistency.blocks, "slot").size() && !Dyn::Has(consistency.whole, "persona"));
|
||||
const Plan::LensPlan persona = Plan::PlanLens(Plan::PersonaLens, draftUnits, draft.lines, {}, {}, "Thomas Colliers", std::vector<std::string>{"R1"});
|
||||
Check("persona: the instance is persona:<name>, its jobs carry the persona after the lens file, the whole pass on the fable tier, no block for an excluded unit",
|
||||
Dyn::Str(persona.whole.at("lens")) == "persona:Thomas Colliers" && Dyn::Str(persona.whole.at("persona")) == "Thomas Colliers" && !Units(persona.blocks).contains("R1")
|
||||
&& Units(persona.blocks).contains("R2") && Dyn::Str(persona.whole.at("model")) == Plan::ModelFor("fable") && Dyn::Str(persona.whole.at("label")) == "persona:Thomas Colliers:whole"
|
||||
&& std::ranges::all_of(persona.blocks, [](const Json& j) { return j.at("persona") == "Thomas Colliers"; }),
|
||||
DumpCompact(persona.whole));
|
||||
Check("the same keys in the same order as a plain job, the persona after lensFile",
|
||||
[&] {
|
||||
std::vector<std::string> keys;
|
||||
for (auto it = persona.whole.begin(); it != persona.whole.end(); ++it) keys.push_back(it.key());
|
||||
return keys == std::vector<std::string>{"label", "lens", "lensFile", "persona", "kind", "unit", "expectedIds", "model", "maxTurns", "timeoutMin"};
|
||||
}());
|
||||
|
||||
// ---- text reuse keys ------------------------------------------------------------
|
||||
const Refs::Table refs = Refs::ParseRefs(Dyn::ReadText(fix / "draft-camera.txt"));
|
||||
Check("refs: the draft's two numbered references", refs.size() == 2 && refs[0].first == "1" && refs[0].second.starts_with("https://lore.kernel.org/r/") && refs[1].first == "2");
|
||||
const std::string r2 = Plan::UnitText(draft.lines, Unit(draftUnits, "R2"));
|
||||
const std::string anchor = Plan::UnitText(draft.lines, Unit(draftUnits, "anchor"));
|
||||
const Json job = {{"kind", "block"}, {"unit", "R2"}};
|
||||
const std::vector<std::string> deps = Plan::TextDeps(r2, draftUnits, draft.lines, refs, anchor);
|
||||
Check("text deps of R2: the [2] it cites, the anchor (a dated unit), the R1 it names - and itself, as a reversal names its own number",
|
||||
deps.size() == 4 && deps[0].starts_with("ref[2]=https://lore.kernel.org/all/") && deps[1] == std::format("anchor={}", Plan::Sha(anchor)) && deps[2].starts_with("unit[R1]=")
|
||||
&& deps[3] == std::format("unit[R2]={}", Plan::Sha(r2)),
|
||||
Show(deps));
|
||||
const std::optional<std::string> k1 = Plan::ReuseKey(job, "facts.md", r2, deps, {"w1"}, "cs", 1);
|
||||
Refs::Table refs2 = refs;
|
||||
refs2[1].second = "https://lore.kernel.org/all/changed@example/";
|
||||
const std::string undated = "A sentence with no date, version, state word or citation at all.";
|
||||
const Json jd = {{"kind", "block"}, {"unit", "R9"}};
|
||||
Check("same unit, same deps -> same key; a cited URL, the anchor of a dated unit, or the dispositions in force change it; the anchor leaves an undated unit's key alone",
|
||||
k1 == Plan::ReuseKey(job, "facts.md", r2, Plan::TextDeps(r2, draftUnits, draft.lines, refs, anchor), {"w1"}, "cs", 1)
|
||||
&& k1 != Plan::ReuseKey(job, "facts.md", r2, Plan::TextDeps(r2, draftUnits, draft.lines, refs2, anchor), {"w1"}, "cs", 1)
|
||||
&& k1 != Plan::ReuseKey(job, "facts.md", r2, Plan::TextDeps(r2, draftUnits, draft.lines, refs, std::format("{}x", anchor)), {"w1"}, "cs", 1)
|
||||
&& Plan::ReuseKey(jd, "facts.md", undated, Plan::TextDeps(undated, draftUnits, draft.lines, refs, anchor), {"w1"}, "cs", 1)
|
||||
== Plan::ReuseKey(jd, "facts.md", undated, Plan::TextDeps(undated, draftUnits, draft.lines, refs, std::format("{}x", anchor)), {"w1"}, "cs", 1)
|
||||
&& k1 != Plan::ReuseKey(job, "facts.md", r2, deps, {"w1", "w2"}, "cs", 1));
|
||||
std::vector<std::string> sortedDeps = deps;
|
||||
std::ranges::sort(sortedDeps);
|
||||
Check("the key is the formula it always was: lens | unit | its text | its deps | the dispositions | the charter | the prompt version | the model | effort slot max",
|
||||
k1 == Plan::Sha(Dyn::Join(std::vector<std::string>{"facts.md", "R2", Plan::Sha(r2), Plan::Sha(Dyn::Join(sortedDeps, "\n")), "w1", "cs", "1", "", "max"}, "|")).substr(0, 16)
|
||||
&& Plan::ReuseKeyEffortSlot == "max");
|
||||
const Json px = {{"kind", "block"}, {"unit", "R2"}, {"persona", "X"}};
|
||||
const Json py = {{"kind", "block"}, {"unit", "R2"}, {"persona", "Y"}};
|
||||
const std::optional<std::string> kp = Plan::ReuseKey(px, Plan::PersonaLens, r2, {}, {}, "c", 1);
|
||||
Check("a persona block has a key of its own per persona; a persona whole pass, a consistency block and a slot never get one",
|
||||
kp && kp != Plan::ReuseKey(py, Plan::PersonaLens, r2, {}, {}, "c", 1) && kp == Plan::ReuseKey(px, Plan::PersonaLens, r2, {}, {}, "c", 1)
|
||||
&& !Plan::ReuseKey({{"kind", "whole"}, {"unit", nullptr}, {"persona", "X"}}, Plan::PersonaLens, r2, {}, {}, "c", 1) && !Plan::ReuseKey(job, "consistency.md", r2, {}, {}, "c", 1)
|
||||
&& !Plan::ReuseKey({{"kind", "slot"}, {"unit", "whole:route"}}, "facts.md", r2, {}, {}, "c", 1));
|
||||
|
||||
// ---- series code keys (the push gates' rule, on the commits fixture) ------------
|
||||
const fs::path src = checkout / "tests" / "Goldens" / "fixture" / "commits";
|
||||
const std::vector<std::string> names = Artifact::PatchFiles(src);
|
||||
std::map<std::string, std::string> orig;
|
||||
for (const std::string& f : names) orig[f] = Dyn::ReadText(src / f);
|
||||
auto variant = [&](std::string_view name, const std::function<std::string(std::int64_t, const std::string&)>& edit, std::string_view prefix = {}) {
|
||||
const fs::path d = base / name;
|
||||
fs::create_directories(d);
|
||||
for (std::size_t i = 0; i < names.size(); ++i) Write(d / std::format("{}{}", prefix, names[i]), edit(static_cast<std::int64_t>(i) + 1, orig[names[i]]));
|
||||
return d;
|
||||
};
|
||||
auto skeys = [&](const fs::path& d, std::string_view lens = "bug-prover.md", std::string_view who = {}) {
|
||||
const Inventory::Built inv = Inventory::Build("series", d.string());
|
||||
const Plan::CommitCode cc = Plan::CommitCodeOf(Artifact::PatchTexts(d), true);
|
||||
std::map<std::string, std::string> out;
|
||||
for (const Json& u : inv.out.at("units")) {
|
||||
if (Plan::KindFor(lens, u) != "code") continue;
|
||||
Json j = {{"kind", "block"}, {"unit", u.at("id")}, {"unitKind", "code"}};
|
||||
if (!who.empty()) j["persona"] = who;
|
||||
out[Dyn::Str(u.at("id"))] = Plan::CodeReuseKey(j, lens, Plan::UnitText(inv.lines, u), cc, {}, "c", 1).value_or("");
|
||||
}
|
||||
return out;
|
||||
};
|
||||
const std::map<std::string, std::string> s0 = skeys(src);
|
||||
auto changed = [&](const std::map<std::string, std::string>& k) {
|
||||
std::set<std::string> out;
|
||||
for (const auto& [id, key] : s0) {
|
||||
if (k.at(id) != key) out.insert(id);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
Check("series: every code unit of a code lens has a key - the README hunk too (documentation is code to the kernel lenses) and the two slots",
|
||||
std::ranges::all_of(s0, [](const auto& e) { return !e.second.empty(); }) && s0.contains("p2:README.md#1") && s0.contains("p2:absent") && s0.contains("p1:outside"));
|
||||
auto sub = [](std::string t, std::string_view a, std::string_view b) { return Dyn::Replace(t, a, b); };
|
||||
const std::map<std::string, std::string> reword = skeys(variant("reword", [&](std::int64_t n, const std::string& t) {
|
||||
return n == 2 ? sub(sub(sub(t, "The greeting takes a name now", "greet() takes a name now"), "From 2222", "From 3333"), "[PATCH 2/2]", "[PATCH v2 2/2]") : t;
|
||||
}));
|
||||
const std::set<std::string> rewordChanged = changed(reword);
|
||||
Check("series: a reworded message, a new commit id and a re-roll counter -> no hunk and no `outside` asked again; that patch's `absent` is (it looks into the message)",
|
||||
rewordChanged == std::set<std::string>{"p2:absent"}, Show({rewordChanged.begin(), rewordChanged.end()}));
|
||||
Check("series: a re-roll that changes only the [PATCH vN n/m] counter and the commit ids asks nothing again",
|
||||
skeys(variant("counter", [&](std::int64_t, const std::string& t) { return sub(sub(sub(t, "[PATCH 2/2]", "[PATCH v2 2/2]"), "[PATCH 1/2]", "[PATCH v2 1/2]"), "From 2222", "From 3333"); })) == s0);
|
||||
const std::map<std::string, std::string> code = skeys(variant("code", [&](std::int64_t n, const std::string& t) { return n == 2 ? sub(t, "+ return \"hello \" + name", "+ return \"hi \" + name") : t; }));
|
||||
const std::set<std::string> codeChanged = changed(code);
|
||||
Check("series: one changed line -> its hunk and the four slots", codeChanged == std::set<std::string>{"p2:hello.py#1", "p1:outside", "p1:absent", "p2:outside", "p2:absent"},
|
||||
Show({codeChanged.begin(), codeChanged.end()}));
|
||||
Check("series: the three code lenses and each persona have keys of their own; better-solution's blocks too; consistency's never",
|
||||
std::set<std::string>{skeys(src, "bug-prover.md").at("p2:hello.py#1"), skeys(src, "code-quality.md").at("p2:hello.py#1"), skeys(src, "minimalism.md").at("p2:hello.py#1"),
|
||||
skeys(src, Plan::PersonaLens, "A B").at("p2:hello.py#1"), skeys(src, Plan::PersonaLens, "C D").at("p2:hello.py#1")}
|
||||
.size() == 5
|
||||
&& skeys(src, Plan::PersonaLens, "A B") == skeys(src, Plan::PersonaLens, "A B") && !skeys(src, "better-solution.md").at("p2:hello.py#1").empty()
|
||||
&& skeys(src, "better-solution.md").at("p2:hello.py#1") != s0.at("p2:hello.py#1") && skeys(src, "consistency.md").at("p2:hello.py#1").empty());
|
||||
const Plan::CommitCode cmt = Plan::CommitCodeOf(Artifact::PatchTexts(src));
|
||||
const Plan::CommitCode ker = Plan::CommitCodeOf(Artifact::PatchTexts(src), true);
|
||||
Check("series: nothing is documentation (a .md hunk is part of its patch's code); the message is kept for `absent`; the push gates' code moves not",
|
||||
ker.docs.at(1).empty() && ker.docs.at(2).empty() && ker.code.at(2) != cmt.code.at(2) && ker.kernel && !cmt.kernel && ker.message.at(2) != ker.message.at(1) && !cmt.docs.at(2).empty());
|
||||
const fs::path reroll = variant("reroll", [](std::int64_t, const std::string& t) { return t; }, "v3-");
|
||||
Check("a re-rolled series (v3-0002-...) is numbered like 0002-...: the partition and the patch texts",
|
||||
Artifact::PatchTexts(reroll).size() == 2 && Artifact::PatchTexts(reroll).contains(2) && Ids(Inventory::Build("series", reroll.string()).out.at("units")) == Ids(Inventory::Build("series", src.string()).out.at("units")));
|
||||
const Inventory::Built sinv = Inventory::Build("series", src.string());
|
||||
const std::string tm = Plan::UnitText(sinv.lines, Unit(sinv.out.at("units"), "p2:msg"));
|
||||
const Json jm = {{"kind", "block"}, {"unit", "p2:msg"}, {"unitKind", "text"}};
|
||||
std::vector<std::string> tdeps = Plan::TextDeps(tm, sinv.out.at("units"), sinv.lines, {}, "");
|
||||
std::ranges::sort(tdeps);
|
||||
Check("series: a text unit keeps the key it always had, and a persona reading it has its own",
|
||||
Plan::ReuseKey(jm, "facts.md", tm, Plan::TextDeps(tm, sinv.out.at("units"), sinv.lines, {}, ""), {}, "c", 1)
|
||||
== Plan::Sha(Dyn::Join(std::vector<std::string>{"facts.md", "p2:msg", Plan::Sha(tm), Plan::Sha(Dyn::Join(tdeps, "\n")), "", "c", "1", "", "max"}, "|")).substr(0, 16)
|
||||
&& Plan::ReuseKey({{"kind", "block"}, {"unit", "p2:msg"}, {"unitKind", "text"}, {"persona", "A B"}}, Plan::PersonaLens, tm, Plan::TextDeps(tm, sinv.out.at("units"), sinv.lines, {}, ""), {}, "c", 1)
|
||||
!= Plan::ReuseKey(jm, "facts.md", tm, Plan::TextDeps(tm, sinv.out.at("units"), sinv.lines, {}, ""), {}, "c", 1));
|
||||
|
||||
// ---- dispositions --------------------------------------------------------------
|
||||
{
|
||||
const fs::path td = base / "disp";
|
||||
Write(td / "0001-a.patch", "x\nANCHOR TEXT\ny\nANCHOR TEXT\n");
|
||||
const std::vector<Json> e = {{{"id", "a"}, {"hash", "H1"}},
|
||||
{{"id", "b"}, {"hash", "H0"}, {"carry", true}, {"anchors", {{{"file", "0001-a.patch"}, {"text", "ANCHOR TEXT"}, {"count", 2}}}}},
|
||||
{{"id", "c"}, {"hash", "H0"}, {"carry", true}, {"anchors", {{{"file", "0001-a.patch"}, {"text", "ANCHOR TEXT"}, {"count", 1}}}}},
|
||||
{{"id", "d"}, {"hash", "H0"}}};
|
||||
auto statuses = [](const std::vector<Json>& rows) {
|
||||
std::map<std::string, std::string> out;
|
||||
for (const Json& r : rows) out[Dyn::Str(r.at("id"))] = Dyn::Str(r.at("status"));
|
||||
return out;
|
||||
};
|
||||
const std::vector<Json> seriesRows = Dispositions::Classify(e, "H1", td, "series");
|
||||
Check("series: hash match in-force, count match carried, count mismatch lapsed, no carry lapsed; a reply never carries",
|
||||
statuses(seriesRows) == std::map<std::string, std::string>{{"a", "in-force"}, {"b", "carried"}, {"c", "lapsed"}, {"d", "lapsed"}}
|
||||
&& statuses(Dispositions::Classify(e, "H1", td, "reply")) == std::map<std::string, std::string>{{"a", "in-force"}, {"b", "lapsed"}, {"c", "lapsed"}, {"d", "lapsed"}}
|
||||
&& Dyn::Str(Dispositions::Classify(e, "H1", td, "reply")[1].at("detail")).contains("no carry in reply mode"));
|
||||
const std::vector<Json> inf = Dispositions::InForce(e, seriesRows);
|
||||
const std::string blk = Dispositions::Block(inf, seriesRows, "facts.md", "H1", "camera");
|
||||
Check("a kernel lens's block: the ledger sentence, the carried entry, lore in the rules, the owner as Gate 5, no disposition-review duty",
|
||||
blk.contains("journal/camera/ledger.md for your lens that is NOT listed here is LAPSED") && blk.contains("CARRIED to this hash") && blk.contains("- a [") && blk.contains("lore via utilities/lore.py")
|
||||
&& blk.contains("Gate 5 must see it") && blk.contains("do NOT emit disposition-review notes"),
|
||||
blk);
|
||||
const std::string repoBlk = Dispositions::Block(inf, seriesRows, Plan::FactsLens, "H1");
|
||||
Check("a repo lens's block is as it was: no journal line, no lore, the owner as Gate 4", repoBlk.contains("Nothing outside this list") && !repoBlk.contains("lore") && repoBlk.contains("Gate 4 must see it"));
|
||||
const std::vector<Json> e2 = {{{"id", "r"}, {"hash", "H1"}, {"kind", "refuted"}, {"lenses", {"etiquette.md"}}}};
|
||||
const std::vector<Json> rows2 = {{{"id", "r"}, {"status", "in-force"}}};
|
||||
const std::string personaBlk = Dispositions::Block(e2, rows2, Plan::PersonaLens, "H1", "camera");
|
||||
Check("a refuted entry is hidden from other lenses, shown to its own and to a persona, whose rules ask for a disposition-review note per entry",
|
||||
Dispositions::Block(e2, rows2, "facts.md", "H1", "camera").contains("(none)") && Dispositions::Block(e2, rows2, "etiquette.md", "H1", "camera").contains("- r [")
|
||||
&& personaBlk.contains("- r [") && personaBlk.contains("kind=disposition-review") && personaBlk.contains("wouldComment"));
|
||||
}
|
||||
|
||||
// ---- prompts --------------------------------------------------------------------
|
||||
const std::string st = Prompt::SystemText("commits");
|
||||
const std::string stk = Prompt::SystemText("series");
|
||||
Check("system text: the kernel variant (a patch series, lore.py, the milos clone); the reply variant; the repo variant without them",
|
||||
stk.contains("patch series") && stk.contains("lore.py") && stk.contains("milos") && Prompt::SystemText("reply").contains("mailing-list reply") && st.contains("commit range") && !st.contains("lore.py")
|
||||
&& Prompt::SystemText() == st);
|
||||
const std::string h = Dyn::Str(series.out.at("hash8"));
|
||||
const std::vector<std::string> trees = {"work/linux", "work/milos-linux"};
|
||||
const std::vector<std::string> reviewers = {"A B", "C D"};
|
||||
const std::string rl = Prompt::RunLayerKernel("series", "journal/x/patch/y/v1", "camera", "work/linux @ abc", reviewers, "2026-10-31", trees, h, series.lines, series.out.at("units"),
|
||||
"REFERENCES prefetched: (none - the artifact cites no lore URLs)");
|
||||
Check("run layer (series): the artifact, the ledger, the thread mboxes, the captures, the base, the trees, the archaeology and lore rules, the reviewers, the date; the inventory names the partition tool; the digest last",
|
||||
rl.contains(std::format("- The frozen artifact (review THIS, at hash {}): the *.patch files in journal/x/patch/y/v1 - inlined below in sorted order; nothing else in that dir is an input", h))
|
||||
&& rl.contains("- Ledger: journal/camera/ledger.md (the only mutable journal file you may read)") && rl.contains("- Thread mboxes: journal/camera/captures/threads/")
|
||||
&& rl.contains("- Evidence/proof artifacts: journal/camera/captures/ (index in the ledger)") && rl.contains("- Target tree at the submission base: work/linux @ abc")
|
||||
&& rl.contains("- Trees: work/linux, work/milos-linux") && rl.contains("- History archaeology ONLY in work/milos-linux") && rl.contains("- lore.kernel.org ONLY via utilities/lore.py")
|
||||
&& rl.contains("- Reviewers with a persona in this run: A B, C D") && rl.contains("- Run date: 2026-10-31") && rl.contains("(utilities/inventory.py: every non-blank line")
|
||||
&& rl.ends_with("REFERENCES prefetched: (none - the artifact cites no lore URLs)") && rl.contains("\n0001| From 3333"),
|
||||
Dyn::Slice(rl, 0, 1200));
|
||||
const std::string rlr = Prompt::RunLayerKernel("reply", "journal/x/draft.txt", "camera", "", {}, "2026-10-31", trees, "H", draft.lines, draftUnits, "R");
|
||||
Check("run layer (reply): the draft file, the base from the artifact, no reviewers line",
|
||||
rlr.contains("the reply draft file journal/x/draft.txt - inlined below; nothing else is an input") && rlr.contains("- Target tree at the submission base: see the artifact") && !rlr.contains("Reviewers with a persona"));
|
||||
const std::string rlc = Prompt::RunLayer("journal/ims/push/x", "abc123", "2026-09-25", std::vector<std::string>{"work/prepush-trees/imsd-1234"}, h, series.lines, series.out.at("units"), {}, "imsd", "ims");
|
||||
const std::string rlcInputs = rlc.substr(0, rlc.find("\n\nARTIFACT (frozen"));
|
||||
Check("run layer (commits, a subsystem): the ledger and captures lines in INPUTS, nothing of lore or milos there",
|
||||
rlcInputs.contains("- Ledger: journal/ims/ledger.md") && rlcInputs.contains("- Evidence/proof artifacts: journal/ims/captures/") && !rlcInputs.contains("lore") && !rlcInputs.contains("milos")
|
||||
&& !Prompt::RunLayer("x", "", "d", std::vector<std::string>{"."}, h, series.lines, series.out.at("units")).substr(0, 400).contains("journal/"));
|
||||
const Plan::LensPlan bs = Plan::PlanLens("better-solution.md", series.out.at("units"), series.lines);
|
||||
const Plan::LensPlan bp = Plan::PlanLens("bug-prover.md", series.out.at("units"), series.lines);
|
||||
const Plan::LensPlan et = Plan::PlanLens("etiquette.md", draftUnits, draft.lines);
|
||||
auto jobOf = [](const std::vector<Json>& jobs, std::string_view unit) -> const Json& {
|
||||
for (const Json& j : jobs) {
|
||||
if (j.at("unit") == unit) return j;
|
||||
}
|
||||
throw std::runtime_error(std::format("no job {}", unit));
|
||||
};
|
||||
const std::string tailWholeFirst = Prompt::Tail(bs.whole, series.out.at("units"), series.lines);
|
||||
const std::string tailWholeAll = Prompt::Tail(consistency.whole, draftUnits, draft.lines);
|
||||
const std::string tailPersona = Prompt::Tail(persona.whole, draftUnits, draft.lines);
|
||||
Check("whole tails: the approach (first), the consistency whole (all) and the persona whole, each listing the slots, with the kernel vocabulary - the persona's its own",
|
||||
tailWholeFirst.contains("judge the artifact as an APPROACH") && tailWholeFirst.contains("- whole:cover-vs-patches [0-0]") && tailWholeFirst.contains("Coverage vocabulary for this lens: clear")
|
||||
&& tailWholeAll.contains("WHOLE PASS of the consistency lens") && tailWholeAll.contains("whole:route") && tailPersona.contains("predictedReview") && tailPersona.contains("`notMine`")
|
||||
&& tailPersona.contains("Coverage vocabulary for a maintainer or reviewer: would-require") && !tailPersona.contains("Coverage vocabulary for this lens"));
|
||||
Check("block tails: the anchor and refs hints, the kernel outside/absent hints, the text hint, the one scope phrase for every unit kind",
|
||||
Prompt::Tail(jobOf(facts.blocks, "anchor"), draftUnits, draft.lines).contains("fixes the as-of date") && Prompt::Tail(jobOf(facts.blocks, "refs"), draftUnits, draft.lines).contains("numbering is contiguous")
|
||||
&& Prompt::Tail(jobOf(bp.blocks, "p1:outside"), series.out.at("units"), series.lines).contains("at the submission base, outside its own diff")
|
||||
&& Prompt::Tail(jobOf(bp.blocks, "p1:absent"), series.out.at("units"), series.lines).contains("a trailer, a test statement, a Cc")
|
||||
&& Prompt::Tail(jobOf(facts.blocks, "R2"), draftUnits, draft.lines).contains("prefetched raw files") && !Prompt::Tail(jobOf(facts.blocks, "R2"), draftUnits, draft.lines).contains("INPUTS names")
|
||||
&& Prompt::Tail(jobOf(facts.blocks, "R2"), draftUnits, draft.lines).contains("a claim at its source, a sentence against its proof obligation, a hunk against the standard")
|
||||
&& Prompt::Tail(jobOf(bp.blocks, "p1:drivers/nfc/widget.c#1"), series.out.at("units"), series.lines).contains("a claim at its source, a sentence against its proof obligation, a hunk against the standard"));
|
||||
Check("slot tails: each lens's duty on its slot",
|
||||
Prompt::Tail(jobOf(et.blocks, "whole:asks"), draftUnits, draft.lines).contains("Step 0 and the point map") && Prompt::Tail(jobOf(facts.blocks, "whole:route"), draftUnits, draft.lines).contains("claims that span units")
|
||||
&& Prompt::Tail(jobOf(Plan::PlanLens("minimalism.md", series.out.at("units"), series.lines).blocks, "whole:absent"), series.out.at("units"), series.lines).contains("series-level minimalism")
|
||||
&& Prompt::Tail(jobOf(Plan::PlanLens("conciseness.md", draftUnits, draft.lines).blocks, "whole:absent"), draftUnits, draft.lines).contains("growth calibration")
|
||||
&& Prompt::SlotDuty("process-docs.md", "whole:asks").contains("get_maintainer") && Prompt::SlotDuty("process-docs.md", "whole:absent").empty()
|
||||
&& !Prompt::Tail(jobOf(facts.blocks, "whole:asks"), draftUnits, draft.lines).contains("for this lens that"));
|
||||
const std::string ll = Prompt::LensLayer("facts.md", "CHARTER TEXT", "DISPOSITIONS BLOCK");
|
||||
const std::string llp = Prompt::LensLayer(Plan::PersonaLens, "CHARTER TEXT", "DISPOSITIONS BLOCK", {}, "the digest", {}, "Thomas Colliers", "lore.py search ...\nlisting");
|
||||
Check("lens layer: a persona's layer opens with who they are and the calibration map, then the charter, the dispositions and the whole pass's digest; a plain one with the charter",
|
||||
llp.starts_with("Your persona: Thomas Colliers. You ARE this person") && llp.contains("\n\nCALIBRATION MAP - a lore search listing of this person's recent mail (fetch the bodies you need):\nlore.py search ...\nlisting\n\nYOUR CHARTER (process/lenses/maintainer-persona.md), verbatim:\n\nCHARTER TEXT\n\nDISPOSITIONS BLOCK\n\nThe whole pass of this lens on this hash concluded")
|
||||
&& ll.starts_with("YOUR CHARTER (process/lenses/facts.md), verbatim:\n\nCHARTER TEXT\n\nDISPOSITIONS BLOCK") && Prompt::LensLayer("facts.md", "C", "D", {}, {}, {}, "P", {}).contains("Your persona: P.")
|
||||
&& !Prompt::LensLayer("facts.md", "C", "D", {}, {}, {}, "P", {}).contains("CALIBRATION MAP"));
|
||||
const std::string up = Prompt::UserPrompt("TAIL", Json({{"maxTurns", 60}}), {}, true);
|
||||
Check("user prompt: the kernel budget line says to read the prefetched raw files; the push gates' does not",
|
||||
up.contains("Tool-call budget for this pass: 60") && up.contains("Batch independent reads into one call, read the prefetched raw files instead of fetching, and return the structured output")
|
||||
&& Prompt::UserPrompt("TAIL", Json({{"maxTurns", 60}})).contains("Batch independent reads into one call and return the structured output"));
|
||||
|
||||
// ---- the schema -----------------------------------------------------------------
|
||||
const std::string wholeK = Schema::SchemaJson("whole", "series");
|
||||
Check("the kernel schema: the persona vocabulary, the kernel note kinds, wouldComment, predictedReview and notMine; the push gates' schema as it was",
|
||||
wholeK.contains("\"would-require\"") && wholeK.contains("\"ledger-row\"") && wholeK.contains("\"disposition-review\"") && wholeK.contains("\"wouldComment\"") && wholeK.contains("\"predictedReview\"")
|
||||
&& wholeK.contains("\"notMine\"") && wholeK.contains("objective wrongness, or a predicted reviewer comment") && Schema::SchemaJson("block", "reply").contains("\"not-mine\"")
|
||||
&& !Schema::SchemaJson("block", "commits").contains("would-require") && Schema::SchemaJson("block") == Schema::SchemaJson("block", "commits") && !Schema::SchemaJson("whole").contains("notMine"));
|
||||
|
||||
// ---- normalize and the matrix ----------------------------------------------------
|
||||
{
|
||||
const Json inv = Json::array({{{"id", "R1"}}, {{"id", "R2"}}, {{"id", "whole:route"}}});
|
||||
const std::vector<std::string> ids = {"R1", "R2", "whole:route"};
|
||||
Json v = {{"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()}, {"coverage", {{{"item", "R1"}, {"verdict", "would-object"}, {"basis", "x"}}}}};
|
||||
const Json n = Normalize::NormalizeOutput(v, {}, std::string(Plan::PersonaLens), "h", Json::array({inv[0]}), ids);
|
||||
Json v2 = {{"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()}, {"coverage", {{{"item", "R1"}, {"verdict", "no-comment"}, {"basis", "no record"}}}}, {"predictedReview", "r"}, {"notMine", {"R2"}}};
|
||||
const Json n2 = Normalize::NormalizeOutput(v2, {}, std::string(Plan::PersonaLens), "h", Json::array({inv[0]}), ids);
|
||||
Json v3 = {{"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()}, {"coverage", {{{"item", "R1"}, {"verdict", "defect"}, {"basis", "x"}}}}};
|
||||
const Json n3 = Normalize::NormalizeOutput(v3, {}, std::string(Plan::PersonaLens), "h", Json::array({inv[0]}), ids);
|
||||
Json v4 = {{"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()}, {"coverage", {{{"item", "R1"}, {"verdict", "would-object"}, {"basis", "x"}}}}};
|
||||
const Json n4 = Normalize::NormalizeOutput(v4, {}, "facts.md", "h", Json::array({inv[0]}), ids);
|
||||
Check("a persona's would-object without a finding is synthesized (fail closed), its no-comment accepted, a generic verdict logged as outside its vocabulary; the persona's review and not-mine list come through",
|
||||
n.at("verdict") == "FIX FIRST" && Dyn::Truthy(n.at("findings")[0].at("synthesized")) && n2.at("verdict") == "SEND-READY" && !Dyn::Truthy(n2.at("normalizeLogs")) && n2.at("predictedReview") == "r"
|
||||
&& n2.at("notMine") == Json({"R2"}) && Dyn::Str(n3.at("normalizeLogs")[0]).contains("not in the vocabulary (would-require/no-comment/") && n3.at("verdict") == "FIX FIRST"
|
||||
&& !Dyn::Has(n4, "predictedReview") && Dyn::Str(n4.at("normalizeLogs")[0]).contains("(clear/defect/immaterial/n/a)"),
|
||||
DumpCompact(n3.at("normalizeLogs")));
|
||||
const Json pa = {{"partLabel", "persona:X:block:R1"}, {"partKind", "block"}, {"partUnit", "R1"}, {"verdict", "FIX FIRST"},
|
||||
{"findings", {{{"finding", "R2 contradicts R1"}, {"evidence", "e"}, {"proposedAction", "p"}, {"items", {"R1", "R2"}}, {"discharged", false}}}},
|
||||
{"notes", Json::array()}, {"coverage", {{{"item", "R1"}, {"verdict", "would-object"}, {"basis", "b"}}}}, {"coverageMissing", Json::array()}, {"undischarged", 1}};
|
||||
const Json pb = {{"partLabel", "persona:X:block:R2"}, {"partKind", "block"}, {"partUnit", "R2"}, {"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()},
|
||||
{"coverage", {{{"item", "R2"}, {"verdict", "no-comment"}, {"basis", "m1@x"}}}}, {"coverageMissing", Json::array()}};
|
||||
const Json m = Normalize::MergeParts("persona:X", std::string(Plan::PersonaLens), std::vector<Json>{pa, pb}, {});
|
||||
std::map<std::string, std::string> cov;
|
||||
for (const Json& c : m.at("coverage")) cov[Dyn::Str(c.at("item"))] = Dyn::Str(c.at("verdict"));
|
||||
Check("merge: a unit a persona's undischarged finding cites is raised to would-object, not defect", cov.at("R2") == "would-object" && Dyn::Str(m.at("normalizeLogs")[0]).contains("raised to would-object"), DumpCompact(m.at("coverage")));
|
||||
const Json pw = {{"partLabel", "persona:X:whole"}, {"partKind", "whole"}, {"partUnit", nullptr}, {"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()},
|
||||
{"coverage", {{{"item", "whole:route"}, {"verdict", "no-comment"}, {"basis", "w"}}}}, {"coverageMissing", Json::array()}};
|
||||
const Json pbk = {{"partLabel", "persona:X:block:R1"}, {"partKind", "block"}, {"partUnit", "R1"}, {"verdict", "SEND-READY"}, {"findings", Json::array()}, {"notes", Json::array()},
|
||||
{"coverage", {{{"item", "R1"}, {"verdict", "immaterial"}, {"basis", "w"}}}}, {"coverageMissing", Json::array()}};
|
||||
Json pbk2 = pbk;
|
||||
pbk2["coverage"] = {{{"item", "R1"}, {"verdict", "no-comment"}, {"basis", "later"}}};
|
||||
const Json m4 = Normalize::MergeParts("persona:X", std::string(Plan::PersonaLens), std::vector<Json>{pw, pbk, pbk2}, {});
|
||||
std::map<std::string, std::string> cov4;
|
||||
for (const Json& c : m4.at("coverage")) cov4[Dyn::Str(c.at("item"))] = Dyn::Str(c.at("verdict"));
|
||||
const Json mx = Normalize::Matrix(inv, std::vector<Json>{m4});
|
||||
Json fy = m4;
|
||||
fy["lens"] = "persona:Y";
|
||||
fy["coverage"] = {{{"item", "R1"}, {"verdict", "no-comment"}, {"basis", " No Record "}}, {{"item", "R2"}, {"verdict", "not-mine"}, {"basis", "no record"}}};
|
||||
const Json mx2 = Normalize::Matrix(inv, std::vector<Json>{m4, fy});
|
||||
Check("the strongest verdict per unit wins (immaterial > no-comment); the matrix: a unit without an entry is MISSING, a hole and a persona hole; a not-mine is unowned too; thin where one persona owns it; modelled where every persona rests on \"no record\"",
|
||||
cov4.at("R1") == "immaterial" && mx.at("matrix").at("R2").at("persona:X").at("verdict") == "MISSING" && mx.at("holes") == Json({"R2"}) && mx.at("personaHoles") == Json({"R2"})
|
||||
&& mx.at("thin") == Json({"R1", "whole:route"}) && mx.at("modelled") == Json::array() && mx2.at("thin") == Json({"whole:route"}) && mx2.at("modelled") == Json::array()
|
||||
&& mx2.at("personaHoles") == Json({"R2"}) && mx2.at("holes") == Json({"R2"}) && Dyn::Has(mx2, "notReviewed"),
|
||||
DumpCompact(mx2));
|
||||
Json gy = fy;
|
||||
gy["coverage"] = {{{"item", "R1"}, {"verdict", "no-comment"}, {"basis", "no record"}}, {{"item", "R2"}, {"verdict", "no-comment"}, {"basis", "NO RECORD"}}, {{"item", "whole:route"}, {"verdict", "no-comment"}, {"basis", "m"}}};
|
||||
const Json mx3 = Normalize::Matrix(inv, std::vector<Json>{gy});
|
||||
Check("...modelled: every persona entry of the unit says no record, whatever the case or spacing", mx3.at("modelled") == Json({"R1", "R2"}) && mx3.at("thin") == Json({"R1", "R2", "whole:route"}));
|
||||
const Json keys = Normalize::Matrix(inv, std::vector<Json>{});
|
||||
std::vector<std::string> order;
|
||||
for (auto it = keys.begin(); it != keys.end(); ++it) order.push_back(it.key());
|
||||
Check("the matrix's keys, in order", order == std::vector<std::string>{"units", "lenses", "holes", "personaHoles", "thin", "modelled", "notReviewed", "defects", "matrix"}, Show(order));
|
||||
}
|
||||
|
||||
// ---- references ---------------------------------------------------------------------
|
||||
{
|
||||
const Refs::Table lore = Refs::ParseRefs("see https://lore.kernel.org/all/a@b/ and <https://lore.kernel.org/linux-arm-msm/c@d> again https://lore.kernel.org/all/a@b/ http://lore.kernel.org/r/e@f");
|
||||
Check("refs: without a numbered list every lore URL once, in order, L1..; the message id of each spelling; a trailing slash kept in the whole match",
|
||||
lore.size() == 3 && lore[0].first == "L1" && lore[0].second == "https://lore.kernel.org/all/a@b/" && lore[1].second == "https://lore.kernel.org/linux-arm-msm/c@d" && lore[2].first == "L3"
|
||||
&& lore[2].second == "http://lore.kernel.org/r/e@f" && Refs::MsgidOf("https://lore.kernel.org/r/7927bdab@kernel.org/") == "7927bdab@kernel.org"
|
||||
&& Refs::MsgidOf("https://lore.kernel.org/all/x@y") == "x@y" && !Refs::MsgidOf("https://example.org/all/x@y/") && Refs::UrlOf(refs, 2) == refs[1].second && !Refs::UrlOf(refs, 3),
|
||||
DumpCompact(Json(lore)));
|
||||
Check("refs: a numbered list wins over lore URLs in the body, the last line of a number wins, a non-URL token is the reference as given",
|
||||
Refs::ParseRefs("[1] https://a/\nhttps://lore.kernel.org/all/z@z/\n[2] foo\n[1] https://b/").size() == 2 && Refs::ParseRefs("[1] https://a/\n[1] https://b/")[0].second == "https://b/"
|
||||
&& Refs::ParseRefs("[2] foo")[0].second == "foo" && Refs::ParseRefs("[2]foo").empty());
|
||||
Check("safe names and headers", Refs::SafeName("a/b c@d.e-f_g") == "a_b_c@d.e-f_g" && Refs::SafeName(std::string(300, 'x')).size() == 200
|
||||
&& Refs::Headers("From: A <a@x>\nSubject: s\nFrom: B\nDate: d\n\nbody From: C") == std::map<std::string, std::string>{{"From", "A <a@x>"}, {"Subject", "s"}, {"Date", "d"}});
|
||||
const fs::path refsDir = base / "refs";
|
||||
std::vector<std::string> calls;
|
||||
std::mutex callsLock;
|
||||
Refs::SetFetcher([&](const fs::path&, const std::string& url, const fs::path& path) -> std::pair<std::string, std::string> {
|
||||
{
|
||||
// The fetchers run on the prefetch's worker threads.
|
||||
const std::lock_guard guard(callsLock);
|
||||
calls.push_back(url);
|
||||
}
|
||||
if (url.contains("fail")) return {"failed", "no such message"};
|
||||
Write(path, "From: Mark <m@x>\nDate: Mon\nSubject: Re: the thing\n\nbody\n");
|
||||
return {"fetched", ""};
|
||||
});
|
||||
std::vector<std::string> logs;
|
||||
const Refs::Table table = {{"1", "https://lore.kernel.org/all/one@x/"}, {"2", "https://lore.kernel.org/all/fail@x/"}, {"3", "https://example.org/not-lore"}};
|
||||
const Json index = Refs::Prefetch(base, table, refsDir, base / "refs-h8.json", [&](const std::string& l) { logs.push_back(l); }, 2);
|
||||
Check("prefetch: a lore reference fetched and its headers read, a failure listed with its error, a non-lore URL not prefetched; the index written; the log",
|
||||
index.at("1").at("status") == "fetched" && index.at("1").at("From") == "Mark <m@x>" && index.at("1").at("Subject") == "Re: the thing" && index.at("1").at("bytes") == 56
|
||||
&& index.at("2").at("status") == "failed" && index.at("2").at("error") == "no such message" && index.at("3").at("status") == "not-prefetched (not a lore URL)" && calls.size() == 2
|
||||
&& fs::exists(base / "refs-h8.json") && logs.size() == 1 && logs[0] == "refs: 1/3 lore references available locally (1 fetched now)",
|
||||
DumpCompact(index));
|
||||
calls.clear();
|
||||
const Json again = Refs::Prefetch(base, table, refsDir, base / "refs-h8.json", [&](const std::string&) {}, 2);
|
||||
Check("...a cached reference is not fetched again", again.at("1").at("status") == "cached" && calls == std::vector<std::string>{table[1].second});
|
||||
const std::string digest = Refs::Digest(index);
|
||||
Check("the digest: the header, then one line per reference - numbered ones first - with the headers and the path, FETCH FAILED, or the status",
|
||||
digest.starts_with("REFERENCES prefetched (read the raw file instead of fetching; a FETCH FAILED line means the reference must be fetched by you or reported as unverifiable):\n[1] one@x | From: Mark <m@x> | Date: Mon | Subject: Re: the thing | ")
|
||||
&& digest.contains(" (56 B)\n[2] fail@x | FETCH FAILED | https://lore.kernel.org/all/fail@x/\n[3] https://example.org/not-lore | not-prefetched (not a lore URL)"),
|
||||
digest);
|
||||
Json mixed = Json::object();
|
||||
mixed["L2"] = {{"url", "u2"}, {"status", "x"}};
|
||||
mixed["10"] = {{"url", "u10"}, {"status", "x"}};
|
||||
mixed["L1"] = {{"url", "u1"}, {"status", "x"}};
|
||||
mixed["2"] = {{"url", "u2"}, {"status", "x"}};
|
||||
Check("...numbered references in numeric order, then the others", Refs::Digest(mixed).contains("\n[2] u2 | x\n[10] u10 | x\n[L1] u1 | x\n[L2] u2 | x"));
|
||||
Refs::SetSearcher([&](const fs::path&, const std::string& q) -> std::pair<std::string, std::string> {
|
||||
calls.push_back(q);
|
||||
return q.contains("Nobody") ? std::pair(std::string("No results.\n"), std::string("err")) : std::pair(std::string("2026-09-01 Re: x\n2026-09-03 Re: y\n"), std::string());
|
||||
});
|
||||
logs.clear();
|
||||
const auto [none, emptyCorpus] = Refs::PersonaCorpus(base, "Nobody Here", refsDir, "20251031", [&](const std::string& l) { logs.push_back(l); });
|
||||
const auto [path, corpus] = Refs::PersonaCorpus(base, "Mark Brown", refsDir, "20251031", [&](const std::string& l) { logs.push_back(l); });
|
||||
calls.clear();
|
||||
const auto [again2, corpus2] = Refs::PersonaCorpus(base, "Mark Brown", refsDir, "20251031", [&](const std::string&) {});
|
||||
Check("a persona corpus: no results is not cached and says so; results are cached under the slug with the query line, and read from the cache after",
|
||||
!none && emptyCorpus.empty() && logs[0] == "persona corpus: Nobody Here -> NO RESULTS for 'f:\"Nobody Here\" AND d:20251031..' (stderr: err) - the whole pass calibrates by its own searches"
|
||||
&& path && path->filename() == "persona-mark-brown.txt" && corpus.starts_with("lore.py search 'f:\"Mark Brown\" AND d:20251031..' (") && corpus.ends_with("2026-09-01 Re: x\n2026-09-03 Re: y\n")
|
||||
&& logs[1].starts_with("persona corpus: Mark Brown -> ") && logs[1].ends_with(" (33 chars)") && calls.empty() && corpus2 == corpus,
|
||||
Dyn::Join(logs, " | "));
|
||||
Check("since: 365 days before the run date", Refs::SinceOf("2026-10-31") == "20251031" && Refs::SinceOf("2024-03-01") == "20230302");
|
||||
Refs::SetFetcher(nullptr);
|
||||
Refs::SetSearcher(nullptr);
|
||||
}
|
||||
|
||||
// ---- the state's persona pass ---------------------------------------------------
|
||||
{
|
||||
const Json disk = {{"version", 1}, {"entries", Json::object()}, {"personaPass", nullptr}};
|
||||
const Json m = State::MergeState(disk, Json::object(), {}, nullptr, Json({{"hash", "H2"}}));
|
||||
Check("personaPass set when given and kept when not", m.at("personaPass") == Json({{"hash", "H2"}}) && State::MergeState(m, Json::object()).at("personaPass") == Json({{"hash", "H2"}}));
|
||||
}
|
||||
|
||||
// ---- the allowlist --------------------------------------------------------------
|
||||
{
|
||||
const std::vector<std::string> al = Pool::AllowedTools("facts.md", "block", std::vector<std::string>{"work/milos-linux"}, {}, std::nullopt, "none");
|
||||
const std::vector<std::string> bp = Pool::AllowedTools("bug-prover.md", "block", std::vector<std::string>{"work/milos-linux"}, {}, std::nullopt, "none");
|
||||
const std::vector<std::string> rp = Pool::AllowedTools(std::string(Plan::FactsLens), "block", std::vector<std::string>{"."}, {}, std::nullopt, "none");
|
||||
const std::vector<std::string> pp = Pool::AllowedTools(std::string(Plan::PersonaLens), "block", std::vector<std::string>{"work/milos-linux"}, {}, std::nullopt, "none");
|
||||
auto has = [](const std::vector<std::string>& l, std::string_view x) { return std::ranges::contains(l, std::string(x)); };
|
||||
Check("allowlist: a kernel lens fetches from lore, a lens that judges against the list searches too, a repo lens has no lore; the tree's git verbs in both spellings",
|
||||
has(al, "Bash(python3 utilities/lore.py fetch:*)") && has(al, "Bash(python3 utilities/lore.py search:*)") && has(al, "Bash(git -C work/milos-linux show:*)") && has(al, "Bash(cd:*)")
|
||||
&& has(bp, "Bash(python3 utilities/lore.py fetch:*)") && !has(bp, "Bash(python3 utilities/lore.py search:*)") && std::ranges::none_of(rp, [](const std::string& x) { return x.contains("lore.py"); })
|
||||
&& has(pp, "Bash(python3 utilities/lore.py search:*)") && std::ranges::none_of(al, [](const std::string& x) { return x.starts_with("Read("); }));
|
||||
}
|
||||
|
||||
// ---- dry runs of both modes through the binary -------------------------------------
|
||||
{
|
||||
const fs::path bench = base / "bench";
|
||||
fs::create_directories(bench / ".review-gates");
|
||||
Json lenses = Json::object();
|
||||
const std::array<std::string_view, 10> on = {"facts.md", "etiquette.md", "consistency.md", "conciseness.md", "better-solution.md", "code-quality.md", "minimalism.md", "bug-prover.md", "process-docs.md", Plan::PersonaLens};
|
||||
for (const std::string_view lf : on) lenses[Plan::LensName(lf)] = {{"run", true}};
|
||||
Write(bench / ".review-gates" / "config.json", DumpFile(Json({{"lenses", lenses}})));
|
||||
const fs::path fakeDir = base / "fake";
|
||||
const fs::path fake = fakeDir / "claude";
|
||||
Write(fake, FakeClaude);
|
||||
fs::permissions(fake, fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec | fs::perms::others_read | fs::perms::others_exec);
|
||||
const std::map<std::string, std::string> env = With(Env, {{"FAKE_DIR", fakeDir.string()}});
|
||||
// The reply: a draft under the journal, its references indexed
|
||||
// as failed (no lore here), a reviewer with no corpus.
|
||||
const fs::path draftPath = bench / "journal" / "x" / "draft.eml";
|
||||
Write(draftPath, Dyn::ReadText(fix / "reply.eml"));
|
||||
const std::string replyHash = Artifact::Sha256File(draftPath);
|
||||
FailedIndex(bench / "journal" / "x" / "gate-state", replyHash.substr(0, 8), Refs::ParseRefs(Dyn::ReadText(draftPath)));
|
||||
const fs::path scratch = base / "scratch";
|
||||
auto run = [&](std::vector<std::string> args) {
|
||||
std::vector<std::string> argv = {Platform::SelfExe().string(), "run"};
|
||||
std::ranges::move(args, std::back_inserter(argv));
|
||||
argv.insert(argv.end(), {"--date", "2026-10-31", "--claude-bin", fake.string(), "--scratch", scratch.string(), "--seats", "local", "--no-probe"});
|
||||
return Process::Run(argv, bench, std::chrono::seconds(120), {}, env);
|
||||
};
|
||||
const Process::Captured replyDry = run({"--mode", "reply", "--subsystem", "x", "--artifact", "journal/x/draft.eml", "--hash", replyHash, "--reviewers", "Some Maintainer", "--dry-run"});
|
||||
const fs::path replyDryDir = scratch / std::format("{}-run-1-dry", replyHash.substr(0, 8));
|
||||
Json replyPlan;
|
||||
try {
|
||||
replyPlan = Dyn::LoadJsonFile(replyDryDir / "dry-run.json");
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
const std::string replySystems = SystemFiles(replyDryDir);
|
||||
const std::string replyPrompts = Prompts(replyDryDir);
|
||||
const Json& byLens = replyPlan.is_object() ? replyPlan.at("dryRun").at("jobsByLens") : Json::object();
|
||||
Check("a reply dry run: exit 2; Gate 3's four lenses and the persona instance planned (whole + blocks); the common text is the reply's; the run layer names the ledger and carries the failed reference; the persona's system file names them and the private-contributor text; no lore fetch attempted",
|
||||
replyDry.returnCode == 2 && replyPlan.is_object() && Dyn::Has(byLens, "facts") && Dyn::Has(byLens, "etiquette") && Dyn::Has(byLens, "consistency") && Dyn::Has(byLens, "conciseness")
|
||||
&& Dyn::Has(byLens, "persona:Some Maintainer") && byLens.at("persona:Some Maintainer").get<std::int64_t>() > 1 && replySystems.contains("pre-send pipeline for a Linux kernel mailing-list reply")
|
||||
&& replySystems.contains("- Ledger: journal/x/ledger.md (the only mutable journal file you may read)") && replySystems.contains("| FETCH FAILED | https://lore.kernel.org/all/20260815.4567@example.org/")
|
||||
&& replySystems.contains("Your persona: Some Maintainer.") && replySystems.contains("lore.py search for mail from Some Maintainer since 20251031: NO RESULTS") && replySystems.contains("journal/x/ledger.md indexes every message")
|
||||
&& replyPrompts.contains("read the whole artifact as they would") && replyPrompts.contains("Coverage vocabulary for a maintainer or reviewer")
|
||||
&& replyPlan.at("overall") == "DRY RUN - NOT SEND-READY (no process launched)" && replyPlan.at("mode") == "reply" && replyPlan.at("refs").at("failed") == Json({"1"})
|
||||
&& Dyn::Has(replyPlan.at("coverage"), "personaHoles") && replyPlan.at("lensesOff") == Json::array() && Dyn::Truthy(replyPlan.at("dryRun").at("ok")),
|
||||
std::format("exit {}; {}{}", replyDry.returnCode, Dyn::Slice(replyDry.out, -1500), replyDry.err));
|
||||
const std::string replyLog = Platform::ReadFile(replyDryDir / "log.txt").value_or("");
|
||||
Check("...the log: the mode, the index loaded without a fetch, the persona corpus missing, the gates and the persona gate",
|
||||
replyLog.contains(": reply journal/x/draft.eml @ ") && replyLog.contains("refs: 1 entries loaded from the existing index (dry run, no fetch)") && replyLog.contains("persona corpus: Some Maintainer -> NO RESULTS")
|
||||
&& replyLog.contains("Gate 3 — Artifact truth: facts, etiquette, consistency, conciseness") && replyLog.contains("Gate 4 — Personas: persona:Some Maintainer"),
|
||||
replyLog);
|
||||
// The series: the fixture under the journal, its cover's
|
||||
// reference indexed.
|
||||
const fs::path seriesDir = bench / "journal" / "x" / "patch" / "widget" / "v1";
|
||||
for (const std::string& f : Artifact::PatchFiles(fix / "series")) Write(seriesDir / f, Dyn::ReadText(fix / "series" / f));
|
||||
const std::string seriesHash = Artifact::ArtifactHash("series", seriesDir);
|
||||
FailedIndex(seriesDir / "gate-state", seriesHash.substr(0, 8), Refs::ParseRefs(Dyn::Join(Inventory::Build("series", seriesDir.string()).lines, "\n")));
|
||||
const Process::Captured seriesDry = run({"--mode", "series", "--subsystem", "x", "--artifact", "journal/x/patch/widget/v1", "--base", "work/linux @ abc", "--dry-run"});
|
||||
const fs::path seriesDryDir = scratch / std::format("{}-run-1-dry", seriesHash.substr(0, 8));
|
||||
Json seriesPlan;
|
||||
try {
|
||||
seriesPlan = Dyn::LoadJsonFile(seriesDryDir / "dry-run.json");
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
const std::string seriesSystems = SystemFiles(seriesDryDir);
|
||||
const Json& sByLens = seriesPlan.is_object() ? seriesPlan.at("dryRun").at("jobsByLens") : Json::object();
|
||||
Check("a series dry run: exit 2; the three gates' nine lenses planned, no persona without reviewers; the series common text, the base and the default trees in the run layer; every record carries its reuse key",
|
||||
seriesDry.returnCode == 2 && seriesPlan.is_object() && sByLens.size() == 9 && Dyn::Has(sByLens, "better-solution") && Dyn::Has(sByLens, "minimalism") && Dyn::Has(sByLens, "process-docs")
|
||||
&& seriesSystems.contains("pre-send pipeline for a Linux kernel patch series") && seriesSystems.contains("- Target tree at the submission base: work/linux @ abc")
|
||||
&& seriesSystems.contains("- Trees: work/linux, work/milos-linux") && seriesSystems.contains("| FETCH FAILED | https://lore.kernel.org/all/20260901.1234@example.org/")
|
||||
&& seriesPlan.at("args").at("effort") == "max" && seriesPlan.at("args").at("reads") == 3 && seriesPlan.at("mode") == "series"
|
||||
&& std::ranges::any_of(seriesPlan.at("processes"), [](const Json& r) { return r.at("kind") == "block" && Dyn::Get(r, "unit") == "p1:drivers/nfc/widget.c#1" && Dyn::Truthy(Dyn::Get(r, "reuseKey")); })
|
||||
&& std::ranges::any_of(seriesPlan.at("processes"), [](const Json& r) { return Dyn::Get(r, "unit") == "cover:2" && Dyn::Truthy(Dyn::Get(r, "reuseKey")); }),
|
||||
std::format("exit {}; {}{}", seriesDry.returnCode, Dyn::Slice(seriesDry.out, -1500), seriesDry.err));
|
||||
const Process::Captured noSubsystem = run({"--mode", "series", "--artifact", "journal/x/patch/widget/v1", "--dry-run"});
|
||||
const Process::Captured withReviewers = run({"--mode", "commits", "--artifact", "journal/x/patch/widget/v1", "--reviewers", "A", "--dry-run"});
|
||||
const Process::Captured replyReads = run({"--mode", "reply", "--subsystem", "x", "--artifact", "journal/x/draft.eml", "--reads", "3", "--dry-run"});
|
||||
Check("preflight: a kernel mode needs --subsystem; commits refuses --reviewers; a reply is read once",
|
||||
noSubsystem.returnCode == 3 && noSubsystem.err.contains("series mode: --subsystem is required") && withReviewers.returnCode == 3 && withReviewers.err.contains("no persona gate")
|
||||
&& replyReads.returnCode == 3 && replyReads.err.contains("a reply has no code units and is read once"),
|
||||
noSubsystem.err + withReviewers.err + replyReads.err);
|
||||
// A full run of the reply on the stand-in: gates clean without a
|
||||
// persona pass is GATES CLEAN; with the reviewer, SEND-READY and
|
||||
// the persona pass recorded.
|
||||
const Process::Captured plain = run({"--mode", "reply", "--subsystem", "x", "--artifact", "journal/x/draft.eml", "--no-reuse", "--no-salvage"});
|
||||
const Json plainDump = Dyn::LoadJsonFile(bench / "journal" / "x" / "gate-state" / std::format("reply-{}-run-1.json", replyHash.substr(0, 8)));
|
||||
const Process::Captured withPersona = run({"--mode", "reply", "--subsystem", "x", "--artifact", "journal/x/draft.eml", "--reviewers", "Some Maintainer", "--no-reuse", "--no-salvage"});
|
||||
const Json personaDump = Dyn::LoadJsonFile(bench / "journal" / "x" / "gate-state" / std::format("reply-{}-run-2.json", replyHash.substr(0, 8)));
|
||||
const Json state = Dyn::LoadJsonFile(bench / "journal" / "x" / "gate-state" / "unit-state.json");
|
||||
std::int64_t personaEntries = 0;
|
||||
for (auto it = state.at("entries").begin(); it != state.at("entries").end(); ++it) {
|
||||
if (Dyn::Get(it.value(), "persona") == "Some Maintainer") ++personaEntries;
|
||||
}
|
||||
Check("a full reply run on the stand-in: gates clean without a reviewer is GATES CLEAN (exit 2); with the reviewer the persona gate runs and passes - SEND-READY, exit 0, personaPass recorded, the persona's blocks stored with its name, the matrix without persona holes",
|
||||
plain.returnCode == 2 && plainDump.at("overall") == "GATES CLEAN - NOT SEND-READY (no reviewers passed, Gate 4 did not run)" && withPersona.returnCode == 0 && personaDump.at("overall") == "SEND-READY"
|
||||
&& Dyn::Get(state, "personaPass").is_object() && state.at("personaPass").at("reviewers") == Json({"Some Maintainer"}) && personaEntries > 0
|
||||
&& personaDump.at("coverage").at("personaHoles") == Json::array() && std::ranges::contains(personaDump.at("coverage").at("lenses"), Json("persona:Some Maintainer"))
|
||||
&& !Dyn::Has(state.at("entries").begin().value(), "reads"),
|
||||
std::format("exit {} / {}; {} / {}; {}{}", plain.returnCode, withPersona.returnCode, Dyn::StrOr(Dyn::Get(plainDump, "overall"), "?"), Dyn::StrOr(Dyn::Get(personaDump, "overall"), "?"),
|
||||
Dyn::Slice(withPersona.out, -800), withPersona.err));
|
||||
}
|
||||
fs::remove_all(base);
|
||||
return Failures ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const std::vector<std::string_view> args(argv, argv + argc);
|
||||
// With arguments this binary is crafter-review itself, in native mode.
|
||||
if (args.size() > 1) {
|
||||
Platform::SystemPaths paths = Platform::Paths();
|
||||
paths.flatpakInfo = "/nonexistent/.flatpak-info";
|
||||
Platform::SetPaths(paths);
|
||||
return Crafter::Review::Main(args);
|
||||
}
|
||||
try {
|
||||
return Checks();
|
||||
} catch (const std::exception& error) {
|
||||
std::println(std::cerr, "kernel modes: {}", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue