feat(config): every lens off until the repository turns it on
Jorijn: "all lenses off by default and opt in only". Config::Defaults() gives every lens run: false; a config that turns on none - a missing `lenses` key included - has the one problem that names the lenses, so config, freeze and run all stop on it; the template ships every lens off, init says to turn lenses on, and the "no lens runs" preflight names the cause. A consumer config without a `lenses` key now needs one, as the CI smoke test's does. Proven with the lens commit before it: the suite (26 passed), and the dry-run comparison recorded there.
This commit is contained in:
parent
febbfc3fcc
commit
101471d81a
15 changed files with 513 additions and 269 deletions
|
|
@ -42,16 +42,21 @@ int main() {
|
|||
fs::create_directories(td);
|
||||
auto load = [&] { return Config::Load(td); };
|
||||
Config::Loaded loaded = load();
|
||||
Check("no file: the defaults, no problem, _path None", loaded.problems.empty() && loaded.config.at("aiTrailers") == "forbid"
|
||||
&& loaded.config.at("functional").at("appUrl") == "http://localhost:8080" && loaded.config.at("_path").is_null());
|
||||
auto noLensOn = [](const std::vector<std::string>& problems) { return problems.size() == 1 && problems[0].starts_with("lenses: no lens is on"); };
|
||||
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); };
|
||||
auto bad = [&](const Json& value) {
|
||||
// A config under test turns the bug-prover on unless it says which lenses
|
||||
// run itself: every lens is off by default, and a config with none on is
|
||||
// a problem of its own (checked below).
|
||||
auto bad = [&](Json value) {
|
||||
if (!value.contains("lenses")) value["lenses"] = {{"repo-bug-prover", {{"run", true}}}};
|
||||
put(value);
|
||||
return load().problems;
|
||||
};
|
||||
put({{"base", "origin/dev"}, {"aiTrailers", "allow"}, {"allowDirty", {"supabase/config.toml"}},
|
||||
put({{"base", "origin/dev"}, {"aiTrailers", "allow"}, {"allowDirty", {"supabase/config.toml"}}, {"lenses", {{"repo-bug-prover", {{"run", true}}}}},
|
||||
{"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"
|
||||
|
|
@ -59,7 +64,8 @@ int main() {
|
|||
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<std::string> problems = bad({{"bogus", 1}, {"functional", {{"nope", 2}, {"appUrl", "ftp://x"}}}, {"aiTrailers", "maybe"}, {"authors", {"nomail"}}});
|
||||
std::vector<std::string> problems = bad({{"bogus", 1}, {"functional", {{"nope", 2}, {"appUrl", "ftp://x"}}}, {"aiTrailers", "maybe"}, {"authors", {"nomail"}},
|
||||
{"lenses", {{"repo-functional", {{"run", true}}}}}});
|
||||
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"}}}}}});
|
||||
|
|
@ -75,8 +81,10 @@ int main() {
|
|||
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();
|
||||
bool allRun = true;
|
||||
for (const Json& entry : loaded.config.at("lenses")) allRun = allRun && entry.at("run") == true;
|
||||
bool noneRun = true;
|
||||
for (const Json& entry : loaded.config.at("lenses")) noneRun = noneRun && entry.at("run") == false;
|
||||
std::vector<std::string> everyLens;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) everyLens.emplace_back(lens.file);
|
||||
bool defaultsMatch = true;
|
||||
std::set<std::string> names;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
|
|
@ -87,25 +95,31 @@ int main() {
|
|||
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"}};
|
||||
Check("lenses: the defaults run all five with none/browser/none/none/none - plan.LENSES' env; the table names every lens of plan.LENSES", loaded.problems.empty() && allRun
|
||||
&& Config::LensesOff(loaded.config).empty() && Dyn::Equal(Config::Environments(loaded.config), environments) && defaultNames == names && defaultsMatch, Show(loaded.problems));
|
||||
put({{"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"environment", "browser"}}}}}});
|
||||
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));
|
||||
put({{"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"run", true}, {"environment", "browser"}}}}}});
|
||||
loaded = load();
|
||||
Check("lenses: run=false merges and lists the lens off in gate order; a static lens may take the browser", loaded.problems.empty()
|
||||
&& Config::LensesOff(loaded.config) == std::vector<std::string>{fn} && Config::Environments(loaded.config).at("repo-bug-prover.md") == "browser"
|
||||
std::vector<std::string> allButProver;
|
||||
for (const Plan::Lens& lens : Plan::Lenses) {
|
||||
if (lens.file != "repo-bug-prover.md") allButProver.emplace_back(lens.file);
|
||||
}
|
||||
Check("lenses: run=true merges and every lens not turned on is listed off in gate order; a static lens may take the browser", loaded.problems.empty()
|
||||
&& Config::LensesOff(loaded.config) == allButProver && 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({{"lenses", {{"nope", {{"run", true}}}, {"repo-better-solution", {{"run", "yes"}, {"environment", "http"}}}}}});
|
||||
problems = bad({{"lenses", {{"nope", {{"run", true}}}, {"repo-better-solution", {{"run", "yes"}, {"environment", "http"}}}, {"repo-bug-prover", {{"run", true}}}}}});
|
||||
Check("lenses: an unknown lens is an unknown key; run must be a bool; environment must be one of none, browser, supabase, browser+supabase", problems.size() == 3
|
||||
&& Any(problems, "lenses.nope") && Any(problems, "run must be") && Any(problems, "one of none, browser, supabase, browser+supabase"), Show(problems));
|
||||
problems = bad({{"lenses", {{"repo-functional", {{"environment", "none"}}}}}});
|
||||
problems = bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "none"}}}}}});
|
||||
Check("the functional lens running with no application under test is a problem; off with none is fine; an unknown environment is reported once",
|
||||
problems.size() == 1 && problems[0].contains("empirical") && bad({{"lenses", {{"repo-functional", {{"run", false}, {"environment", "none"}}}}}}).empty()
|
||||
&& bad({{"lenses", {{"repo-functional", {{"environment", "http"}}}}}}).size() == 1, Show(problems));
|
||||
problems = bad({{"lenses", {{"repo-functional", {{"environment", "supabase"}}}}}});
|
||||
problems.size() == 1 && problems[0].contains("empirical")
|
||||
&& bad({{"lenses", {{"repo-functional", {{"run", false}, {"environment", "none"}}}, {"repo-bug-prover", {{"run", true}}}}}}).empty()
|
||||
&& bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "http"}}}}}}).size() == 1, Show(problems));
|
||||
problems = bad({{"lenses", {{"repo-functional", {{"run", true}, {"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({{"lenses", {{"repo-functional", {{"environment", "browser+supabase"}}}, {"repo-bug-prover", {{"environment", "supabase"}}},
|
||||
{"repo-better-solution", {{"environment", "browser+supabase"}}}}}}).empty(), Show(problems));
|
||||
&& bad({{"lenses", {{"repo-functional", {{"run", true}, {"environment", "browser+supabase"}}}, {"repo-bug-prover", {{"run", true}, {"environment", "supabase"}}},
|
||||
{"repo-better-solution", {{"run", true}, {"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", ""}}));
|
||||
|
|
@ -117,9 +131,10 @@ int main() {
|
|||
&& 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 lens that runs drives the application (plan.ENVIRONMENTS item 4)",
|
||||
Any(bad({{"functional", {{"appUrl", "ftp://x"}}}}), "appUrl")
|
||||
&& bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"environment", "supabase"}}}}}}).empty()
|
||||
&& Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"environment", "browser+supabase"}}}}}}), "appUrl"));
|
||||
Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", true}}}}}}), "appUrl")
|
||||
&& !Any(bad({{"functional", {{"appUrl", "ftp://x"}}}}), "appUrl")
|
||||
&& bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"run", true}, {"environment", "supabase"}}}}}}).empty()
|
||||
&& Any(bad({{"functional", {{"appUrl", "ftp://x"}}}, {"lenses", {{"repo-functional", {{"run", false}}}, {"repo-bug-prover", {{"run", true}, {"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:*)")
|
||||
|
|
@ -129,16 +144,17 @@ int main() {
|
|||
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", problems.size() == 1
|
||||
&& problems[0].contains("must be an object") && Config::LensesOff(load().config).empty() && Config::Environments(load().config).at(fn) == "browser", Show(problems));
|
||||
problems = bad({{"lenses", {{"repo-functional", false}, {"repo-bug-prover", {{"run", true}}}}}});
|
||||
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 allOff = Json::object();
|
||||
for (auto it = defaults.at("lenses").begin(); it != defaults.at("lenses").end(); ++it) allOff[it.key()] = {{"run", false}};
|
||||
problems = bad({{"lenses", allOff}});
|
||||
Check("every lens off is a problem", problems.size() == 1 && problems[0].contains("every lens is off"), Show(problems));
|
||||
Check("every lens off is the problem that names them", problems.size() == 1 && problems[0].contains("no lens is on") && problems[0].contains("repo-facts"), Show(problems));
|
||||
const Json ex = Config::Example();
|
||||
Check("the example validates and is what process/templates/config.json holds", Config::Validate(ex).empty()
|
||||
&& Dyn::Equal(Dyn::LoadJsonFile(fs::current_path() / "process" / "templates" / "config.json"), ex));
|
||||
Check("the example is what process/templates/config.json holds, every lens off, and its only problem says to turn one on", noLensOn(Config::Validate(ex))
|
||||
&& Dyn::Equal(Dyn::LoadJsonFile(fs::current_path() / "process" / "templates" / "config.json"), ex)
|
||||
&& std::ranges::all_of(ex.at("lenses"), [](const Json& entry) { return entry.at("run") == false; }), Show(Config::Validate(ex)));
|
||||
const fs::path repo = td / "r";
|
||||
fs::create_directories(repo);
|
||||
Process::Run({"git", "-C", repo.string(), "init", "-q"});
|
||||
|
|
|
|||
Loading…
Reference in a new issue