// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The commands around a run, this binary as crafter-review: init (writes, // refreshes, keeps what it did not write, replaces the old link into the // submodule, refuses a foreign link, a subdirectory and the home directory // before it writes anything), doc, stop (a scratch dir, --hash on the // board), seats on a claude stand-in, and browser's usage. #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; } void Write(const fs::path& path, std::string_view text) { fs::create_directories(path.parent_path()); std::ofstream(path, std::ios::binary) << text; } std::string Read(const fs::path& path) { return Platform::ReadFile(path).value_or(""); } // A claude that answers a seat probe as the CLI does: a rate_limit_event // with both windows, and on a Fable request the Fable window too. constexpr std::string_view FakeClaude = R"sh(#!/bin/sh model= prev= for a in "$@"; do [ "$prev" = --model ] && model=$a; prev=$a; done cat > /dev/null # A Console key whose credit is gone, answering after two seconds. if [ "$ANTHROPIC_API_KEY" = sk-ant-api-spent ]; then sleep 2 echo '{"type":"result","subtype":"success","is_error":true,"result":"Credit balance is too low","total_cost_usd":0}' exit 1 fi now=$(date +%s) fable= [ "$model" = claude-fable-5-1 ] && fable=",\"seven_day_overage_included\":{\"utilization\":0.3,\"resetsAt\":$((now+172800))}" echo '{"type":"system","subtype":"init","session_id":"0b6f3c56-1111-4222-8333-944455556666"}' echo "{\"type\":\"rate_limit_event\",\"rate_limit_info\":{\"status\":\"allowed\",\"rateLimitType\":\"five_hour\",\"resetsAt\":$((now+3600)),\"unifiedWindows\":{\"five_hour\":{\"utilization\":0.42,\"resetsAt\":$((now+3600))},\"seven_day\":{\"utilization\":0.13,\"resetsAt\":$((now+259200))}$fable}}}" echo '{"type":"result","subtype":"success","is_error":false,"result":"ok","total_cost_usd":0.0001}' )sh"; // The environment of every child: git with no user config, this // checkout as the home, nothing of the shell's review or seat setup. std::map Env; std::map With(std::map env, const std::map& changes) { for (const auto& [k, v] : changes) env[k] = v; return env; } Process::Captured Self(std::vector args, const fs::path& cwd, const std::map& env = {}) { args.insert(args.begin(), Platform::SelfExe().string()); return Process::Run(args, cwd, std::chrono::seconds(60), {}, With(Env, env)); } std::int32_t Checks() { std::println("commands: init, doc, stop, seats on a claude stand-in, browser's usage"); Platform::SetMode(Platform::Mode::Native); const fs::path checkout = fs::current_path(); SetHome(checkout); const fs::path base = fs::temp_directory_path() / std::format("crafter-review-commands-{}", Platform::PublishedPid()); fs::remove_all(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" || k == "CLAUDE_BIN"; const bool seat = k.starts_with("CLAUDE_CODE_OAUTH_TOKEN") || k.starts_with("ANTHROPIC_API_KEY") || k.starts_with("OPENROUTER_KEY"); if (!review && !seat) Env[k] = v; } // No seats file of the machine's own: XDG_CONFIG_HOME natively, and // HOST_XDG_CONFIG_HOME, which the Flatpak's Platform::Init copies // over it - a directory of this run's own, which nothing creates. const std::string noConfig = (base / "no-config").string(); Env = With(Env, {{"XDG_CONFIG_HOME", noConfig}, {"HOST_XDG_CONFIG_HOME", noConfig}, {"GIT_CONFIG_GLOBAL", "/dev/null"}, {"GIT_CONFIG_SYSTEM", "/dev/null"}, {"CRAFTER_REVIEW_HOME", checkout.string()}, {"REVIEW_GATES_BOARD", (base / "board.json").string()}, {"REVIEW_GATES_SCRATCH", (base / "scratch").string()}, {"TZ", "UTC"}}); auto repo = [&](std::string_view name) { const fs::path dir = base / name; fs::create_directories(dir); Process::Run({"git", "init", "-q", "-b", "main", dir.string()}, std::nullopt, std::chrono::seconds(60), {}, Env); return dir; }; const std::string shipped = Read(checkout / "process" / "skill" / "prepush" / "SKILL.md"); const std::string marker = "\nold text\n", marker)); const Process::Captured again = Self({"init"}, r); Check("init again: the config and an entry already ignored are kept, the missing one added once, the skill it wrote refreshed, and the .gitignore beside it", again.returnCode == 0 && Read(r / ".review-gates" / "config.json") == R"({"base": "origin/dev"})" && Read(r / ".gitignore") == "node_modules/\n/.review-gates/push\n.review-gates/trees/\n" && Read(r / ".claude" / "skills" / "prepush" / "SKILL.md") == skill && again.out.contains("refreshed .claude/skills/prepush/SKILL.md") && again.out.contains("refreshed .claude/skills/prepush/.gitignore") && Read(r / ".claude" / "skills" / "prepush" / ".gitignore") == skillIgnore, again.out + again.err); // The repository's own .gitignore is never edited for the skill: one // that ignores .claude wholesale (Crafter.Build) is kept as it is. Write(r / ".gitignore", "node_modules/\n/.review-gates/push\n.review-gates/trees/\n.claude\n"); const Process::Captured wholesale = Self({"init"}, r); Check("init: a repository that ignores .claude wholesale keeps its .gitignore as it is, and the skill its own .gitignore", wholesale.returnCode == 0 && Read(r / ".gitignore") == "node_modules/\n/.review-gates/push\n.review-gates/trees/\n.claude\n" && wholesale.out.contains("kept .gitignore") && Read(r / ".claude" / "skills" / "prepush" / ".gitignore") == skillIgnore, wholesale.out + wholesale.err); // A copy init wrote that the repository tracks (a base-era init // ignored nothing): the note says how to untrack it, on every init. // A hand-written tracked skill is kept and not ignored - the ignore // file the earlier init wrote beside it goes - with no note. const fs::path tracked = repo("tracked"); Self({"init"}, tracked); Process::Run({"git", "-C", tracked.string(), "add", "-f", ".claude/skills/prepush/SKILL.md", ".gitignore"}, std::nullopt, std::chrono::seconds(60), {}, Env); Process::Run({"git", "-C", tracked.string(), "-c", "user.email=me@example.com", "-c", "user.name=Me", "commit", "-q", "-m", "track the skill"}, std::nullopt, std::chrono::seconds(60), {}, Env); const Process::Captured trackedInit = Self({"init"}, tracked); const Process::Captured trackedAgain = Self({"init"}, tracked); Write(tracked / ".claude" / "skills" / "prepush" / "SKILL.md", "my own skill\n"); Process::Run({"git", "-C", tracked.string(), "-c", "user.email=me@example.com", "-c", "user.name=Me", "commit", "-q", "-am", "my own skill"}, std::nullopt, std::chrono::seconds(60), {}, Env); const Process::Captured handWritten = Self({"init"}, tracked); const bool stillIgnored = Process::Run({"git", "-C", tracked.string(), "check-ignore", "--no-index", "-q", ".claude/skills/prepush/SKILL.md"}, std::nullopt, std::chrono::seconds(60), {}, Env).returnCode == 0; Check("init on a tracked skill: one init wrote gets the note with `git rm --cached` on every init; one the repository wrote is kept and no rule ignores it any more - the earlier init's .gitignore beside it removed - and no note", trackedInit.returnCode == 0 && trackedInit.out.contains("git rm --cached .claude/skills/prepush/SKILL.md") && trackedAgain.returnCode == 0 && trackedAgain.out.contains("git rm --cached") && handWritten.returnCode == 0 && handWritten.out.contains("kept .claude/skills/prepush/SKILL.md") && handWritten.out.contains("removed .claude/skills/prepush/.gitignore") && !fs::exists(tracked / ".claude" / "skills" / "prepush" / ".gitignore") && !stillIgnored && !handWritten.out.contains("git rm --cached"), std::format("exits {} {} {}, ignored {}; {}{}{}{}", trackedInit.returnCode, trackedAgain.returnCode, handWritten.returnCode, stillIgnored, trackedInit.out, trackedAgain.out, handWritten.out, handWritten.err)); Write(r / ".claude" / "skills" / "prepush" / "SKILL.md", "my own skill\n"); const Process::Captured kept = Self({"init"}, r); const std::string mine = Read(r / ".claude" / "skills" / "prepush" / "SKILL.md"); const bool keptIgnoreFile = fs::exists(r / ".claude" / "skills" / "prepush" / ".gitignore"); const Process::Captured forced = Self({"init", "--force"}, r); Check("init: a SKILL.md init did not write is kept, and the .gitignore init wrote beside it goes; --force replaces the skill and writes the .gitignore again", kept.returnCode == 0 && mine == "my own skill\n" && kept.out.contains("--force replaces it") && !keptIgnoreFile && forced.returnCode == 0 && Read(r / ".claude" / "skills" / "prepush" / "SKILL.md") == skill && Read(r / ".claude" / "skills" / "prepush" / ".gitignore") == skillIgnore, kept.out + forced.out); // A .gitignore beside the skill that init did not write is kept; // --force replaces it. Write(r / ".claude" / "skills" / "prepush" / ".gitignore", "my own rules\n"); const Process::Captured foreignIgnore = Self({"init"}, r); const std::string ownRules = Read(r / ".claude" / "skills" / "prepush" / ".gitignore"); const Process::Captured forcedIgnore = Self({"init", "--force"}, r); Check("init: a .gitignore beside the skill that init did not write is kept, saying so; --force replaces it", foreignIgnore.returnCode == 0 && ownRules == "my own rules\n" && foreignIgnore.out.contains("kept .claude/skills/prepush/.gitignore (init did not write it: --force replaces it)") && forcedIgnore.returnCode == 0 && Read(r / ".claude" / "skills" / "prepush" / ".gitignore") == skillIgnore, foreignIgnore.out + forcedIgnore.out); // The old setup: the skill dir a link into the submodule. const fs::path old = repo("old"); Write(old / "review-gates" / "process" / "skill" / "prepush" / "SKILL.md", "the submodule's file\n"); fs::create_directories(old / ".claude" / "skills"); fs::create_directory_symlink("../../review-gates/process/skill/prepush", old / ".claude" / "skills" / "prepush"); const Process::Captured relinked = Self({"init"}, old); std::error_code error; Check("init: the old link into the submodule goes - a real directory with the skill in its place, the submodule's file untouched", relinked.returnCode == 0 && !fs::is_symlink(fs::symlink_status(old / ".claude" / "skills" / "prepush", error)) && Read(old / ".claude" / "skills" / "prepush" / "SKILL.md") == skill && Read(old / "review-gates" / "process" / "skill" / "prepush" / "SKILL.md") == "the submodule's file\n" && relinked.out.contains("removed the link .claude/skills/prepush -> ../../review-gates/process/skill/prepush"), relinked.out + relinked.err); // What init refuses, before it writes anything. const fs::path foreign = repo("foreign"); fs::create_directories(base / "elsewhere"); fs::create_directories(foreign / ".claude" / "skills"); fs::create_directory_symlink("../../../elsewhere", foreign / ".claude" / "skills" / "prepush"); const Process::Captured refused = Self({"init"}, foreign); fs::create_directories(r / "sub"); const Process::Captured sub = Self({"init"}, r / "sub"); const fs::path home = repo("home"); const Process::Captured atHome = Self({"init"}, home, {{"HOME", home.string()}}); const bool homeUntouched = !fs::exists(home / ".review-gates"); const Process::Captured atHomeForced = Self({"init", "--force"}, home, {{"HOME", home.string()}}); fs::create_directories(base / "plain"); const Process::Captured plain = Self({"init"}, base / "plain"); Check("init refuses, exit 3, writing nothing: a link that is not into the submodule (--force replaces it), a subdirectory, the home directory (--force goes on), no checkout", refused.returnCode == 3 && refused.err.contains(".claude/skills/prepush is a link to ../../../elsewhere") && !fs::exists(foreign / ".review-gates") && !fs::exists(foreign / ".gitignore") && sub.returnCode == 3 && sub.err.contains("from the repository's toplevel") && !fs::exists(r / "sub" / ".review-gates") && atHome.returnCode == 3 && atHome.err.contains("is your home directory") && homeUntouched && atHomeForced.returnCode == 0 && plain.returnCode == 3 && plain.err.contains("is not in a git checkout"), refused.err + sub.err + atHome.err + plain.err); // A .gitignore beside the skill that is a link - to a file outside // the skill's directory, or dangling - is a link like the skill's // others: refused before anything is written, never written through; // --force replaces the link with init's file and leaves its target as // it was. const fs::path linked = repo("linked"); Write(linked / "outside.ignore", "keep me\n"); fs::create_directories(linked / ".claude" / "skills" / "prepush"); fs::create_symlink("../../../outside.ignore", linked / ".claude" / "skills" / "prepush" / ".gitignore"); const Process::Captured linkRefused = Self({"init"}, linked); const bool linkUntouched = !fs::exists(linked / ".review-gates") && Read(linked / "outside.ignore") == "keep me\n"; const Process::Captured linkForced = Self({"init", "--force"}, linked); std::error_code linkError; const bool replaced = !fs::is_symlink(fs::symlink_status(linked / ".claude" / "skills" / "prepush" / ".gitignore", linkError)) && Read(linked / ".claude" / "skills" / "prepush" / ".gitignore") == skillIgnore && Read(linked / "outside.ignore") == "keep me\n"; const fs::path dangling = repo("dangling"); fs::create_directories(dangling / "shared"); fs::create_directories(dangling / ".claude" / "skills" / "prepush"); fs::create_symlink("../../../shared/skill.ignore", dangling / ".claude" / "skills" / "prepush" / ".gitignore"); const Process::Captured danglingRefused = Self({"init"}, dangling); Check("init: a .gitignore beside the skill that is a link is refused before anything is written and never written through, dangling or not; --force replaces the link with init's file, " "its target untouched", linkRefused.returnCode == 3 && linkRefused.err.contains(".claude/skills/prepush/.gitignore is a link to ../../../outside.ignore") && linkUntouched && linkForced.returnCode == 0 && replaced && danglingRefused.returnCode == 3 && !fs::exists(dangling / "shared" / "skill.ignore") && !fs::exists(dangling / ".review-gates"), linkRefused.err + linkForced.out + linkForced.err + danglingRefused.err); // A .gitignore link whose target names review-gates is the // repository's too - the old setup linked the skill's directory, never // its .gitignore - so it is refused like any other. A link init cannot // remove (its directory not writable, where permissions bind) refuses // under --force before anything is written, nothing written through it. const fs::path named = repo("named"); Write(named / ".review-gates" / "skill.gitignore", "mine\n"); fs::create_directories(named / ".claude" / "skills" / "prepush"); fs::create_symlink("../../../.review-gates/skill.gitignore", named / ".claude" / "skills" / "prepush" / ".gitignore"); const Process::Captured namedRefused = Self({"init"}, named); // A SKILL.md link into the pipeline's own .review-gates/ names the // submodule but does not resolve into it: refused and kept too. const fs::path namedSkill = repo("named-skill"); Write(namedSkill / ".review-gates" / "skill.md", "mine\n"); fs::create_directories(namedSkill / ".claude" / "skills" / "prepush"); fs::create_symlink("../../../.review-gates/skill.md", namedSkill / ".claude" / "skills" / "prepush" / "SKILL.md"); const Process::Captured namedSkillRefused = Self({"init"}, namedSkill); const bool namedSkillKept = fs::is_symlink(fs::symlink_status(namedSkill / ".claude" / "skills" / "prepush" / "SKILL.md", linkError)) && Read(namedSkill / ".review-gates" / "skill.md") == "mine\n" && !fs::exists(namedSkill / ".review-gates" / "config.json"); const bool namedKept = fs::is_symlink(fs::symlink_status(named / ".claude" / "skills" / "prepush" / ".gitignore", linkError)) && Read(named / ".review-gates" / "skill.gitignore") == "mine\n" && !fs::exists(named / ".review-gates" / "config.json"); const fs::path stuck = repo("stuck"); Write(stuck / "outside.ignore", "keep me\n"); fs::create_directories(stuck / ".claude" / "skills" / "prepush"); fs::create_symlink("../../../outside.ignore", stuck / ".claude" / "skills" / "prepush" / ".gitignore"); fs::permissions(stuck / ".claude" / "skills" / "prepush", fs::perms::owner_read | fs::perms::owner_exec); const bool writable = static_cast(std::ofstream(stuck / ".claude" / "skills" / "prepush" / ".probe")); fs::remove(stuck / ".claude" / "skills" / "prepush" / ".probe", linkError); const Process::Captured stuckForced = Self({"init", "--force"}, stuck); fs::permissions(stuck / ".claude" / "skills" / "prepush", fs::perms::owner_all); Check("init: a .gitignore or SKILL.md link into a review-gates path that is not the submodule is refused and kept; a link init cannot remove refuses under --force, writing nothing (where " "permissions bind)", namedRefused.returnCode == 3 && namedRefused.err.contains(".claude/skills/prepush/.gitignore is a link to ../../../.review-gates/skill.gitignore") && namedKept && namedSkillRefused.returnCode == 3 && namedSkillRefused.err.contains(".claude/skills/prepush/SKILL.md is a link to ../../../.review-gates/skill.md") && namedSkillKept && (writable || (stuckForced.returnCode == 3 && stuckForced.err.contains("cannot remove the link .claude/skills/prepush/.gitignore") && Read(stuck / "outside.ignore") == "keep me\n" && !fs::exists(stuck / ".review-gates"))), namedRefused.err + namedSkillRefused.err + stuckForced.out + stuckForced.err); // doc: the texts, and where they are. const Process::Captured prepush = Self({"doc", "prepush"}, base); const Process::Captured where = Self({"doc", "--path", "config"}, base); const Process::Captured missing = Self({"doc"}, base); const Process::Captured wrong = Self({"doc", "nope"}, base); Check("doc: the procedure byte for byte, a template's path, usage errors for no name and an unknown one (exit 3)", prepush.returnCode == 0 && prepush.out == Read(checkout / "process" / "prepush.md") && where.returnCode == 0 && where.out == std::format("{}\n", (checkout / "process" / "templates" / "config.json").string()) && missing.returnCode == 3 && missing.err.contains("the following arguments are required: name") && wrong.returnCode == 3 && wrong.err.contains("argument name: invalid choice: 'nope' (choose from 'prepush', 'manifest', 'config')"), prepush.err + where.out + missing.err + wrong.err); // stop: a live driver is this process; one that is gone, a sleep that // was reaped. const std::string me = Platform::Self()->Key(); const fs::path live = base / "scratch" / "abcd1234-run-1"; Write(live / ".driver", me); const std::int64_t gone = Process::Start({.argv = {"true"}}).pid; Process::Wait(gone); const fs::path dead = base / "scratch" / "abcd1234-run-2"; Write(dead / ".driver", std::format("{}:{}", gone, 1)); const Process::Captured byDir = Self({"stop", live.string()}, base); const bool wroteDir = fs::exists(live / "stop"); fs::remove(live / "stop"); const Process::Captured byDead = Self({"stop", dead.string()}, base); const Process::Captured byNone = Self({"stop", (base / "plain").string()}, base); const Process::Captured neither = Self({"stop"}, base); Check("stop : the stop file for the live driver its .driver names (exit 0); a driver that is gone, a dir no run holds, no target: nothing written", byDir.returnCode == 0 && wroteDir && byDir.out.contains(std::format("stop: driver {}", me)) && byDead.returnCode == 1 && byDead.err.contains("is no longer running") && !fs::exists(dead / "stop") && byNone.returnCode == 1 && byNone.err.contains("no .driver marker") && neither.returnCode == 3, byDir.out + byDir.err + byDead.err + byNone.err); Json drivers = Json::object(); drivers[me] = {{"run", "commits x @ abcd1234"}, {"scratch", live.string()}, {"since", Platform::Now()}, {"boot", Platform::BootId()}}; Write(base / "board.json", DumpFile({{"version", 1}, {"drivers", drivers}, {"seats", Json::object()}})); const std::string boardBefore = Read(base / "board.json"); const Process::Captured byHash = Self({"stop", "--hash", "abcd1234ffff"}, base); const bool wroteHash = fs::exists(live / "stop"); const Process::Captured otherHash = Self({"stop", "--hash", "00000000"}, base); Check("stop --hash: every live run of the hash on the board gets its stop file (the first 8 characters decide); another hash finds none (exit 1); the board is read, never written", byHash.returnCode == 0 && wroteHash && otherHash.returnCode == 1 && otherHash.err.contains("no live run of 00000000 on the board") && Read(base / "board.json") == boardBefore, byHash.out + byHash.err + otherHash.err); // Only `run` and `seats` read the seats file, and its lines reach the // placement readers alone (rounds 8 and 11 of the self-review): a // `stop` with the board path only in the working directory's .env // looks at the machine's default board, as every driver does - under // a HOME of the test's own here. std::map envNoPaths = Env; envNoPaths.erase("REVIEW_GATES_BOARD"); envNoPaths.erase("REVIEW_GATES_SCRATCH"); envNoPaths["HOME"] = (base / "home").string(); const fs::path fileRepo = base / "file-repo"; fs::create_directories(fileRepo); Write(fileRepo / ".env", std::format("REVIEW_GATES_BOARD={}\nREVIEW_GATES_SCRATCH={}\n", (base / "file-board.json").string(), (base / "file-scratch").string())); const Process::Captured fromFile = Process::Run({Platform::SelfExe().string(), "stop", "--hash", "00000000"}, fileRepo, std::chrono::seconds(60), {}, envNoPaths); Check("no line of the seats file reaches `stop`: with the board's and the scratch root's paths only in the working directory's .env it reads the machine's default board", fromFile.returnCode == 1 && fromFile.err.contains(std::format("on the board ({})", (base / "home" / ".cache" / "review-gates" / "seat-board.json").string())) && !fromFile.err.contains("file-board"), fromFile.err); // seats on the stand-in: a token from the env file, an API key, the // CLI's own login. const fs::path fake = base / "claude"; Write(fake, FakeClaude); fs::permissions(fake, fs::perms::owner_all); Write(base / "seats" / ".env", "CLAUDE_CODE_OAUTH_TOKEN1=\"tok-one\"\nANTHROPIC_API_KEY1=sk-ant-api-one\n"); fs::remove(base / "board.json"); const std::map withFake = {{"CLAUDE_BIN", fake.string()}}; const Process::Captured report = Self({"seats", "--local", "--env-file", ".env"}, base / "seats", withFake); const Process::Captured json = Self({"seats", "--json", "--no-fable"}, base / "seats", withFake); // --env (a prefix of --env-file) names the file for the tokens and // the settings alike, with ./.env in the way (the API seat's section // is where the order shows). Write(base / "seats" / "other.env", "CLAUDE_CODE_OAUTH_TOKEN2=tok-two\nANTHROPIC_API_KEY2=sk-ant-api-two\nANTHROPIC_API_KEY2_GRANT_DAY=16\nREVIEW_GATES_SEAT_ORDER=oauth-first\n"); const Process::Captured other = Self({"seats", "--no-fable", "--env", "other.env"}, base / "seats", withFake); // An application's .env (no seat line, no setting) is passed over for // the machine's own seats file. Write(base / "app" / ".env", "VITE_API_URL=http://localhost:54321\n"); Write(base / "app-xdg" / "crafter-review" / "env", "CLAUDE_CODE_OAUTH_TOKEN3=tok-three\n"); const Process::Captured appSeats = Self({"seats", "--no-fable"}, base / "app", {{"CLAUDE_BIN", fake.string()}, {"XDG_CONFIG_HOME", (base / "app-xdg").string()}, {"HOST_XDG_CONFIG_HOME", (base / "app-xdg").string()}}); // A seats file that can be read only once - a FIFO, as `--env-file // <(gpg -d seats.env.gpg)` gives one - is read once: its settings and // its tokens come from that one read (round 10 of the self-review). fs::create_directories(base / "fifo"); const Process::Captured fromFifo = Process::Run( {"sh", "-c", std::format("mkfifo seats.fifo && (printf 'CLAUDE_CODE_OAUTH_TOKEN4=tok-four\\nANTHROPIC_API_KEY4=sk-ant-api-four\\nREVIEW_GATES_SEAT_ORDER=oauth-first\\n' > seats.fifo &) && exec '{}' seats --no-fable --env-file seats.fifo", Platform::SelfExe().string())}, base / "fifo", std::chrono::seconds(60), {}, With(Env, withFake)); Json parsed; try { parsed = Json::parse(json.out); } catch (const std::exception&) { } Check("seats: every seat probed - the CLI's own login, the env file's token, the API key - its windows read, the API seat beside the driver's ledger, the drivers on the board; an application's .env " "is passed over for the machine's own file; a FIFO is read once, its settings and its seats alike", report.returnCode == 0 && report.out.starts_with("seat pool: 3 seat(s) (local, 1, api1) from .env and your own login, probed ") && report.out.contains("| local | 42% |") && report.out.contains("| 1 | 42% |") && report.out.contains("| api1 | ok | ok | $0.00 of $200") && report.out.contains("drivers: none running") && other.returnCode == 0 && other.out.starts_with("seat pool: 2 seat(s) (2, api2) from other.env, probed ") && other.out.contains("| api2 | ok | ok | $0.00 of $200") && other.out.contains("| 16 |") && other.out.contains("oauth seats run first (REVIEW_GATES_SEAT_ORDER=oauth-first)") && !other.out.contains("tok-two") && !other.out.contains("sk-ant-api-two") && appSeats.returnCode == 0 && appSeats.out.starts_with(std::format("seat pool: 1 seat(s) (3) from {}, probed ", (base / "app-xdg" / "crafter-review" / "env").string())) && fromFifo.returnCode == 0 && fromFifo.out.starts_with("seat pool: 2 seat(s) (4, api4) from seats.fifo, probed ") && fromFifo.out.contains("oauth seats run first (REVIEW_GATES_SEAT_ORDER=oauth-first)"), report.out + report.err + other.out + other.err + appSeats.out + appSeats.err + fromFifo.out + fromFifo.err); Check("seats --json: the probe results as JSON - the seats, the API seats, the eligible ones and the next placement; ./.env read by default; no token, no key printed", json.returnCode == 0 && parsed.is_object() && parsed.at("seats").size() == 1 && parsed.at("seats").at(0).at("probedWith") == "haiku" && parsed.at("apiSeats").size() == 1 && parsed.at("eligible") == Json::array({1}) && parsed.at("next").at("seat") == 1 && Dyn::Str(parsed.at("probedAt")).ends_with("Z") && !(report.out + json.out).contains("tok-one") && !(report.out + json.out).contains("sk-ant-api-one"), json.out.substr(0, 600) + json.err); // The credit error a probe reads is recorded in the ledger stamped // when it is written, not at the start the probes share (1.1.0 round // 3): the spent key answers two seconds after the command began. Write(base / "spent" / ".env", "ANTHROPIC_API_KEY3=sk-ant-api-spent\n"); const double spentStart = Platform::Now(); const Process::Captured spent = Self({"seats", "--json"}, base / "spent", withFake); Json spentParsed; try { spentParsed = Json::parse(spent.out); } catch (const std::exception&) { } // The child's TZ is UTC (Env): the stamp is compared in UTC. const auto utc = [](double epoch) { return std::format("{:%Y-%m-%dT%H:%M:%S}", std::chrono::floor(std::chrono::sys_seconds(std::chrono::seconds(static_cast(epoch))))); }; Json spentEntry; if (spentParsed.is_object() && spentParsed.contains("apiSeats") && !spentParsed.at("apiSeats").empty()) { spentEntry = Dyn::Get(Dyn::Get(spentParsed.at("apiSeats").at(0), "ledger"), "outSince"); } Check("seats: a credit error the probe read is recorded in the shared ledger stamped when written, after the probe, never at the probes' common start", spent.returnCode == 0 && spentParsed.is_object() && spentParsed.at("apiSeats").at(0).at("event") == "credit-gone" && spentEntry.is_string() && Dyn::Slice(spentEntry.get(), 0, 19) >= utc(spentStart + 2), spent.out.substr(0, 800) + spent.err); const Process::Captured none = Self({"seats"}, base / "plain", withFake); const Process::Captured noFile = Self({"seats", "--env-file", "missing.env"}, base / "plain", withFake); Write(base / "plain" / "bad.env", std::string("\xff\n", 2)); const Process::Captured badFile = Self({"seats", "--env-file", "bad.env"}, base / "plain", withFake); Check("seats: no seat configured is exit 1 with what it looked for; a missing env file and one that is not UTF-8 are exit 1 with the read error", none.returnCode == 1 && none.out.starts_with("no seat configured (CLAUDE_CODE_OAUTH_TOKEN1..6 / ANTHROPIC_API_KEY1..6; --local probes your own login)") && noFile.returnCode == 1 && noFile.err.contains("FileNotFoundError: [Errno 2] No such file or directory: 'missing.env'") && badFile.returnCode == 1 && badFile.err.contains("UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte"), none.out + noFile.err + badFile.err); // What escapes a command ends it with exit 1, never an abort; config's // help. const Process::Captured noHome = Self({"doc", "prepush"}, base, {{"CRAFTER_REVIEW_HOME", (base / "nowhere").string()}}); const Process::Captured configHelp = Self({"config", "-h"}, base); Check("an error no command catches ends it with exit 1 and its message, never an abort; config -h prints its usage, exit 0", noHome.returnCode == 1 && noHome.err.starts_with("crafter-review: ") && noHome.err.contains("holds no process/lenses") && configHelp.returnCode == 0 && configHelp.out.starts_with("usage: crafter-review config [-h]"), noHome.err + configHelp.out + configHelp.err); // browser: its usage (an install fetches from npm). const Process::Captured noAction = Self({"browser"}, base); const Process::Captured badAction = Self({"browser", "remove"}, base); Check("browser: an action is required, and install is the one there is (exit 3)", noAction.returnCode == 3 && noAction.err.contains("the following arguments are required: action") && badAction.returnCode == 3 && badAction.err.contains("argument action: invalid choice: 'remove' (choose from 'install')"), noAction.err + badAction.err); fs::remove_all(base); return Failures ? 1 : 0; } } int main(int argc, char** argv) { const std::vector args(argv, argv + argc); // With arguments this binary is crafter-review itself, in native mode // whatever builds it. 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, "commands: {}", error.what()); return 1; } }