// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The consumer config: the defaults, the merge, unknown keys, validation, // the lenses (run, environment), authors, the login fixture, the example. #include 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; } bool Any(const std::vector& problems, std::string_view part) { return std::ranges::any_of(problems, [&](const std::string& p) { return p.contains(part); }); } std::string Show(const std::vector& problems) { Json out = Json::array(); for (const std::string& p : problems) out.push_back(p); return Dyn::ReprValue(out); } } int main() { std::println("config: defaults, merge, unknown keys, validation, lenses (run, environment), authors, login fixture, the example"); // A git call of a test reads no global config: this machine's signs // every commit with the owner's key. setenv("GIT_CONFIG_GLOBAL", "/dev/null", 1); setenv("GIT_CONFIG_SYSTEM", "/dev/null", 1); SetHome(fs::current_path()); const std::string fn(Plan::FunctionalLens); const fs::path td = fs::temp_directory_path() / std::format("crafter-review-config-{}", Platform::PublishedPid()); fs::remove_all(td); fs::create_directories(td); auto load = [&] { return Config::Load(td); }; Config::Loaded loaded = load(); auto noLensOn = [](const std::vector& problems) { return problems.size() == 1 && problems[0].starts_with("modes: no mode lists a lens"); }; Check("no file: the defaults, _path None, and the one problem of a repository that turns no lens on (2026-10-31)", noLensOn(loaded.problems) && loaded.config.at("aiTrailers") == "forbid" && loaded.config.at("functional").at("appUrl") == "http://localhost:8080" && loaded.config.at("_path").is_null(), Show(loaded.problems)); fs::create_directories(td / ".review-gates"); const fs::path p = td / ".review-gates" / "config.json"; auto put = [&](const Json& value) { std::ofstream(p, std::ios::binary) << DumpSpaced(value); }; // A config under test lists the bug-prover in the commits mode unless it // defines modes itself: every lens is off until a mode lists it, and a // config with none listed is a problem of its own (checked below). auto bad = [&](Json value) { if (!value.contains("modes")) value["modes"] = {{"commits", {{"lenses", {"repo-bug-prover"}}}}}; put(value); return load().problems; }; put({{"base", "origin/dev"}, {"aiTrailers", "allow"}, {"allowDirty", {"supabase/config.toml"}}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"functional", {{"exclude", {"**/*.test.ts"}}, {"readyPath", "/health"}, {"extraTools", {"Bash(redis-cli:*)", "mcp__x__y"}}}}}); loaded = load(); Check("the file merges over the defaults, nested too", loaded.problems.empty() && loaded.config.at("base") == "origin/dev" && loaded.config.at("aiTrailers") == "allow" && loaded.config.at("functional").at("readyPath") == "/health" && loaded.config.at("functional").at("appUrl") == "http://localhost:8080" && loaded.config.at("_path") == p.string(), Show(loaded.problems)); Check("rangeFrom 'base' without a base branch is a problem; 'sideways' is one too", Any(bad({{"rangeFrom", "base"}}), "needs a base") && Any(bad({{"rangeFrom", "sideways"}, {"base", "origin/dev"}}), "rangeFrom must be")); std::vector problems = bad({{"bogus", 1}, {"functional", {{"nope", 2}, {"appUrl", "ftp://x"}}}, {"aiTrailers", "maybe"}, {"authors", {"nomail"}}, {"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}}); Check("unknown keys and bad values are each a problem", problems.size() == 5 && Any(problems, "bogus") && Any(problems, "functional.nope") && Any(problems, "appUrl") && Any(problems, "aiTrailers") && Any(problems, "authors"), Show(problems)); problems = bad({{"functional", {{"extraTools", {"rm -rf /"}}, {"mcp", {{"args", "x"}}}}}}); Check("extraTools must be Bash(:*) or mcp__...; mcp.args a list", problems.size() == 2, Show(problems)); // A path found on the host is not there inside the Flatpak. const std::vector missingCommand = bad({{"functional", {{"mcp", {{"command", "/nonexistent/bin/npx"}}}}}}); Check("functional.mcp.command: an absolute path that is no program here is a problem; a name, or a program that is there, is not", missingCommand.size() == 1 && missingCommand[0].starts_with("functional.mcp.command '/nonexistent/bin/npx' is no program here") && bad({{"functional", {{"mcp", {{"command", "/bin/sh"}}}}}}).empty() && bad({{"functional", {{"mcp", {{"command", "npx"}}}}}}).empty(), Show(missingCommand)); // A lens's own section (lenses..section, 2026-11-01, Jorijn: a path // per lens rather than a directory listed): a file of the repository's, // appended to the shipped charter. Only the section problems and the // unknown keys count here: a config under test may turn no lens on. auto sectionProblems = [&](const Json& lenses) { std::vector out; for (const std::string& problem : bad({{"lenses", lenses}})) { if (problem.contains("section") || problem.contains("unknown key lenses.")) out.push_back(problem); } return out; }; const std::string outside = "lenses.repo-facts.section must be null or a file inside the repository (a relative path)"; Check("lenses..section: not a string, a blank one, an absolute path and one climbing out with .. are problems (slots.dir's rule) and resolve to nothing", Any(sectionProblems({{"repo-facts", {{"section", 5}}}}), outside) && Any(sectionProblems({{"repo-facts", {{"section", " "}}}}), outside) && Any(sectionProblems({{"repo-facts", {{"section", "/elsewhere/facts.md"}}}}), outside) && Any(sectionProblems({{"repo-facts", {{"section", "../facts.md"}}}}), outside) && Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", "/elsewhere/facts.md"}}}}}}, td).empty() && Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", "a/../../facts.md"}}}}}}, td).empty()); fs::create_directories(td / "sections" / "adir"); std::ofstream(td / "sections" / "facts.md", std::ios::binary) << "## Ours\n"; std::ofstream(td / "sections" / "README.md", std::ios::binary) << "beside the sections, no concern of the config's\n"; std::ofstream(td / "sections" / "bad.md", std::ios::binary) << std::string("\xff\n", 2); fs::create_symlink("missing-target.md", td / "sections" / "dangling.md"); const std::vector named = sectionProblems({{"repo-facts", {{"section", "sections/facts.md"}}}, {"repo-bug-prover", {{"section", "sections/bad.md"}}}, {"repo-security", {{"section", "sections/dangling.md"}}}, {"repo-functional", {{"section", "sections/adir"}}}, {"facts", {{"section", "sections/nope.md"}}}}); Check("lenses..section: a file that is there is fine, whatever lies beside it; one not UTF-8, a dangling link, a directory or a path to nothing is a problem naming the lens and the path", named.size() == 4 && Any(named, "lenses.repo-bug-prover.section: sections/bad.md is not UTF-8") && Any(named, "lenses.repo-security.section: sections/dangling.md cannot be read") && Any(named, "lenses.repo-functional.section: sections/adir cannot be read") && Any(named, "lenses.facts.section: sections/nope.md does not exist"), Show(named)); const std::vector unknown = sectionProblems({{"repo-fact", {{"section", "sections/facts.md"}}}, {"_common", {{"section", "sections/facts.md"}}}, {"repo-facts", {{"sectoin", "sections/facts.md"}}}}); Check("lenses..section: a name that is no lens - the common text's too - and a misspelt key are unknown keys", Any(unknown, "unknown key lenses.repo-fact") && Any(unknown, "unknown key lenses._common") && Any(unknown, "unknown key lenses.repo-facts.sectoin"), Show(unknown)); // A file that cannot be read - where permissions bind, as they do not for // root. std::ofstream(td / "sections" / "locked.md", std::ios::binary) << "## Ours\n"; fs::permissions(td / "sections" / "locked.md", fs::perms::none); const bool unreadable = !Platform::ReadFile(td / "sections" / "locked.md"); const std::vector locked = sectionProblems({{"repo-facts", {{"section", "sections/locked.md"}}}}); fs::permissions(td / "sections" / "locked.md", fs::perms::owner_all); Check("lenses..section: a file that cannot be read is a problem naming it", !unreadable || Any(locked, "lenses.repo-facts.section: sections/locked.md cannot be read"), Show(locked)); const std::map resolved = Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", "sections/facts.md"}}}}}}, td); Check("lenses..section: a file that is there has no problem and resolves against the repository, keyed by the lens's file; null is none", sectionProblems({{"repo-facts", {{"section", "sections/facts.md"}}}}).empty() && resolved.size() == 1 && resolved.at("repo-facts.md") == td / "sections" / "facts.md" && Config::LensSectionsOf({{"lenses", {{"repo-facts", {{"section", nullptr}}}}}}, td).empty()); std::ofstream(p, std::ios::binary) << "{not json"; Check("not JSON -> one problem", load().problems.size() == 1); Check("--config to a missing file is a problem", Any(Config::Load(td, td / "none.json").problems, "no such file")); put(Json::object()); loaded = load(); std::vector everyLens; for (const Plan::Lens& lens : Plan::Lenses) everyLens.emplace_back(lens.file); bool defaultsMatch = true; std::set names; for (const Plan::Lens& lens : Plan::Lenses) { names.insert(Plan::LensName(lens.file)); const Json entry = Config::Defaults().at("lenses").at(Plan::LensName(lens.file)); defaultsMatch = defaultsMatch && entry.at("environment") == lens.env && !entry.contains("run"); } const Json defaults = Config::Defaults(); std::set defaultNames; for (auto it = defaults.at("lenses").begin(); it != defaults.at("lenses").end(); ++it) defaultNames.insert(it.key()); 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("modes: the defaults are the three kinds as modes of their own name with no lens and no effort of their own; every lens is off until a mode lists it (2026-10-31, opt-in only) and the one problem names every lens; lenses hold the environment alone - none/browser/none/none/none", noLensOn(loaded.problems) && loaded.problems[0].contains("repo-facts") && loaded.problems[0].contains("maintainer-persona") && Config::LensesOff(loaded.config) == everyLens && Dyn::Equal(Config::Environments(loaded.config), environments) && defaultNames == names && defaultsMatch && defaults.at("modes").size() == 3 && Dyn::Equal(defaults.at("modes").at("reply"), {{"kind", "reply"}, {"lenses", Json::array()}, {"effort", nullptr}}) && Config::ModeNames(loaded.config) == std::vector{"reply", "series", "commits"} && Config::ModeOf(loaded.config, "commits")->lensFiles.empty() && Config::ModeOf(loaded.config, "commits")->kind == "commits" && !Config::ModeOf(loaded.config, "nope"), Show(loaded.problems)); put({{"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-bug-prover", {{"environment", "browser"}}}}}}); loaded = load(); std::vector allButProver; for (const Plan::Lens& lens : Plan::Lenses) { if (lens.file != "repo-bug-prover.md") allButProver.emplace_back(lens.file); } Check("modes: a kind's mode lists its lenses; the lenses no mode lists, and the kind's lenses the mode leaves out, come in table order; a static lens may take the browser", loaded.problems.empty() && Config::LensesOff(loaded.config) == allButProver && Config::LensesOff(loaded.config, "commits") == std::vector{"repo-better-solution.md", "repo-functional.md", "repo-security.md", "repo-facts.md"} && Config::ModeOf(loaded.config, "commits")->lensFiles == std::vector{"repo-bug-prover.md"} && !Config::ModeOf(loaded.config, "commits")->effort && Config::Environments(loaded.config).at("repo-bug-prover.md") == "browser" && loaded.config.at("lenses").at("repo-functional").at("environment") == "browser", Show(loaded.problems)); problems = bad({{"modes", {{"quick", {{"lenses", {"repo-bug-prover"}}}}}}}); Check("modes: a mode of the repository's own name needs its kind (its lenses cannot be checked without one, so nothing is listed)", problems.size() == 2 && Any(problems, "modes.quick.kind must name the artifact kind") && Any(problems, "no mode lists a lens"), Show(problems)); put({{"modes", {{"quick", {{"kind", "commits"}, {"lenses", {"repo-facts", "repo-bug-prover"}}, {"effort", "high"}}}}}}); loaded = load(); const std::optional quick = Config::ModeOf(loaded.config, "quick"); Check("modes: a named mode is a kind, the lenses it lists in the table's order and its effort; the kinds' own modes stay beside it with no lens", loaded.problems.empty() && quick && quick->kind == "commits" && quick->lensFiles == std::vector{"repo-bug-prover.md", "repo-facts.md"} && quick->effort == "high" && Config::ModeNames(loaded.config) == std::vector{"reply", "series", "commits", "quick"} && Config::LensesOff(loaded.config, "quick") == std::vector{"repo-better-solution.md", "repo-functional.md", "repo-security.md"} && Config::ModeOf(loaded.config, "commits")->lensFiles.empty() && Config::LensesOff(loaded.config, "nope").empty(), Show(loaded.problems)); problems = bad({{"modes", {{"commits", {{"kind", "series"}, {"lenses", {"repo-bug-prover", "facts", "nope", "repo-bug-prover"}}, {"effort", "ultra"}}}}}}); Check("modes: a kind's own mode is that kind; a lens of another kind, a name that is no lens and a lens listed twice are each a problem; the effort is one of the five", problems.size() == 5 && Any(problems, "modes.commits.kind must be commits") && Any(problems, "facts is no lens of a commits") && Any(problems, "nope is no lens of a commits") && Any(problems, "repo-bug-prover is listed twice") && Any(problems, "modes.commits.effort must be one of low, medium, high, xhigh, max"), Show(problems)); Check("modes: not an object, a mode that is no object, lenses that are no list", Any(bad({{"modes", 5}}), "modes must be an object") && Any(bad({{"modes", {{"commits", 5}}}}), "modes.commits must be an object") && Any(bad({{"modes", {{"commits", {{"lenses", "repo-bug-prover"}}}}}}), "modes.commits.lenses must be a list of lens names")); put({{"lenses", {{"repo-bug-prover", {{"run", true}}}}}}); loaded = load(); Check("lenses..run is retired: a warning that says a mode lists what runs, and nothing runs until one does", loaded.config.at("_warnings").size() == 1 && Dyn::Str(loaded.config.at("_warnings").at(0)).contains("lenses.repo-bug-prover.run is no longer read") && noLensOn(loaded.problems), Show(loaded.problems)); problems = bad({{"lenses", {{"nope", {{"environment", "none"}}}, {"repo-better-solution", {{"environment", "http"}}}}}}); Check("lenses: an unknown lens is an unknown key; environment must be one of none, browser, supabase, browser+supabase", problems.size() == 2 && Any(problems, "lenses.nope") && Any(problems, "one of none, browser, supabase, browser+supabase"), Show(problems)); problems = bad({{"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "none"}}}}}}); Check("the functional lens listed by a mode with no application under test is a problem; unlisted with none is fine; an unknown environment is reported once", problems.size() == 1 && problems[0].contains("empirical") && bad({{"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "none"}}}}}}).empty() && bad({{"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "http"}}}}}}).size() == 1, Show(problems)); problems = bad({{"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "supabase"}}}}}}); Check("the database alone is no application under test for the functional lens; browser+supabase is; a static lens may take either, or the database alone", problems.size() == 1 && problems[0].contains("empirical") && problems[0].contains("browser, browser+supabase") && bad({{"modes", {{"commits", {{"lenses", {"repo-functional", "repo-bug-prover", "repo-better-solution"}}}}}}, {"lenses", {{"repo-functional", {{"environment", "browser+supabase"}}}, {"repo-bug-prover", {{"environment", "supabase"}}}, {"repo-better-solution", {{"environment", "browser+supabase"}}}}}}).empty(), Show(problems)); loaded = load(); Check("supabase: the defaults are the local stack's Postgres (54322), no start/stop, no notes", Dyn::Equal(loaded.config.at("supabase"), {{"dbUrl", "postgresql://postgres:postgres@127.0.0.1:54322/postgres"}, {"start", nullptr}, {"stop", nullptr}, {"notes", ""}})); problems = bad({{"supabase", {{"dbUrl", "postgresql://postgres:secret@db.abcdefgh.supabase.co:5432/postgres"}}}}); Check("supabase.dbUrl: a hosted database is refused (this machine only), and the message does not echo the password", problems.size() == 1 && problems[0].contains("not this machine") && !problems[0].contains("secret"), Show(problems)); Check("supabase.dbUrl: http:// is no database URL; localhost and ::1 are this machine; start/stop must be command lines; an unknown key is reported", Any(bad({{"supabase", {{"dbUrl", "http://127.0.0.1:54321"}}}}), "postgres://") && bad({{"supabase", {{"dbUrl", "postgres://u:p@localhost:6543/x"}}}}).empty() && bad({{"supabase", {{"dbUrl", "postgresql://u@[::1]:5432/x"}}}}).empty() && Any(bad({{"supabase", {{"start", {"bash", "x"}}}}}), "supabase.start") && Any(bad({{"supabase", {{"nope", 1}}}}), "supabase.nope")); Check("functional.appUrl is checked only while a listed lens drives the application (plan.ENVIRONMENTS item 4)", Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"modes", {{"commits", {{"lenses", {"repo-functional"}}}}}}}), "appUrl") && !Any(bad({{"functional", {{"appUrl", "ftp://x"}}}}), "appUrl") && bad({{"functional", {{"appUrl", "ftp://x"}}}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-bug-prover", {{"environment", "supabase"}}}}}}).empty() && Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"modes", {{"commits", {{"lenses", {"repo-bug-prover"}}}}}}, {"lenses", {{"repo-bug-prover", {{"environment", "browser+supabase"}}}}}}), "appUrl")); problems = bad({{"functional", {{"extraTools", {"Bash(psql:*)", "Bash(psql -h 127.0.0.1:*)", "Bash(psqlx:*)", "Bash(npm run e2e:*)", "mcp__x__y"}}}}}); Check("functional.extraTools: an entry pool.DENY overrides is a problem (psql commits to the shared database; npm is a package manager); a mere prefix twin is not", problems.size() == 1 && problems[0].contains("Bash(psql:*)") && problems[0].contains("Bash(psql -h 127.0.0.1:*)") && problems[0].contains("Bash(npm run e2e:*)") && !problems[0].contains("psqlx") && problems[0].contains("supabase environment"), Show(problems)); Json withPassword = load().config; withPassword["supabase"] = {{"dbUrl", "postgresql://postgres:hunter2@127.0.0.1:54322/postgres"}, {"start", nullptr}, {"stop", nullptr}, {"notes", ""}}; const Json rc = Config::Redacted(withPassword); Check("redacted: the dump's copy of the config masks the database password and drops _path", rc.at("supabase").at("dbUrl") == "postgresql://postgres:***@127.0.0.1:54322/postgres" && !DumpSpaced(rc).contains("hunter2") && !rc.contains("_path")); problems = bad({{"lenses", {{"repo-functional", false}}}}); Check("a bare false under lenses is a shape problem, never an exception; lenses_off and environments fall back to the defaults (off, browser)", problems.size() == 1 && problems[0].contains("must be an object") && Config::LensesOff(load().config) == allButProver && Config::Environments(load().config).at(fn) == "browser", Show(problems)); Json noLens = Json::object(); for (const std::string_view kind : Plan::Kinds) noLens[std::string(kind)] = {{"lenses", Json::array()}}; problems = bad({{"modes", noLens}}); Check("no mode listing a lens is the problem that names them", problems.size() == 1 && problems[0].contains("no mode lists a lens") && problems[0].contains("repo-facts"), Show(problems)); const Json ex = Config::Example(); Check("the example is what process/templates/config.json holds: the three kinds as modes with every lens of their kind at the kind's effort, no problem", Config::Validate(ex).empty() && Dyn::Equal(Dyn::LoadJsonFile(fs::current_path() / "process" / "templates" / "config.json"), ex) && ex.at("modes").at("commits").at("lenses").size() == 5 && ex.at("modes").at("series").at("lenses").size() == 10 && ex.at("modes").at("reply").at("lenses").size() == 5 && ex.at("modes").at("commits").at("effort") == "xhigh" && ex.at("modes").at("series").at("effort") == "max" && ex.at("modes").at("reply").at("effort") == "max" && std::ranges::none_of(ex.at("lenses"), [](const Json& entry) { return entry.contains("run"); }), Show(Config::Validate(ex))); const fs::path repo = td / "r"; fs::create_directories(repo); Process::Run({"git", "-C", repo.string(), "init", "-q"}); Process::Run({"git", "-C", repo.string(), "config", "user.email", "me@example.com"}); Check("authors: the configured list, else git config user.email", Config::Authors({{"authors", {"a@b.c"}}}, repo) == std::vector{"a@b.c"} && Config::Authors({{"authors", nullptr}}, repo) == std::vector{"me@example.com"}); std::ofstream(td / "u.json", std::ios::binary) << "{}"; Check("login_fixture: the first candidate that exists", Config::LoginFixture({{"functional", {{"login", {{"fixtures", {"nope.json", "u.json"}}}}}}}, td) == "u.json" && !Config::LoginFixture({{"functional", {{"login", {{"fixtures", Json::array()}}}}}}, td)); fs::remove_all(td); return Failures ? 1 : 0; }