Crafter.Review/tests/Seats/main.cpp

259 lines
19 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Seat usage: the pure functions against reference vectors, numbered in
// order, so "check 17" always names the same vector.
import std;
import Crafter.Review;
namespace fs = std::filesystem;
using namespace Crafter::Review;
using namespace Crafter::Review::Seats;
namespace {
std::int32_t Failures = 0;
std::int32_t N = 0;
void Eq(const std::string& got, const std::string& want) {
++N;
if (got != want) {
std::println(std::cerr, "check {}:\n got: {}\nwant: {}", N, Dyn::Repr(got), Dyn::Repr(want));
++Failures;
}
}
void Eq(bool got, bool want) {
Eq(std::string(got ? "True" : "False"), std::string(want ? "True" : "False"));
}
void EqJson(const Json& got, const Json& want) {
++N;
if (!Dyn::Equal(got, want)) {
std::println(std::cerr, "check {}:\n got: {}\nwant: {}", N, DumpCompactSorted(got), DumpCompactSorted(want));
++Failures;
}
}
std::vector<std::string> Lines(const std::string& text) {
return Dyn::Split(text, "\n");
}
Json With(Json value, const Json& changes) {
for (auto it = changes.begin(); it != changes.end(); ++it) value[it.key()] = it.value();
return value;
}
}
int main() {
const Zone utc = ZoneNamed("UTC");
const Zone ams = ZoneNamed("Europe/Amsterdam");
// 2026-09-07 10:00 UTC
constexpr double now = 1788775200.0;
const auto nowS = static_cast<std::int64_t>(now);
const Json cfg = DefaultConfig();
auto seat = [&](std::int32_t k, double five, double seven = 0, const Json& over = Json::object()) {
Json windows = {{"five_hour", {{"utilization", five}, {"resetsAt", nowS + 300 * k}}}, {"seven_day", {{"utilization", seven}, {"resetsAt", nowS + 3 * 86400}}}};
// An integer stays an integer.
for (auto& w : windows) {
const double u = w.at("utilization").get<double>();
if (u == std::floor(u)) w["utilization"] = static_cast<std::int64_t>(u);
}
const auto [util, binding] = SeatUtil(windows);
Json s = {{"seat", k}, {"state", "ok"}, {"windows", windows}, {"util", util}, {"binding", binding}, {"resetsAt", windows.at("five_hour").at("resetsAt")}};
return With(s, over);
};
const std::string fable(FableWindow);
// format_when: the clock in the report zone and a countdown; a reset over
// a day out carries its date.
Eq(FormatWhen(nowS + 45 * 60, now, utc), "10:45 (in 45 min)");
Eq(FormatWhen(nowS + 3 * 3600 + 5 * 60, now, ams), "15:05 (in 3h 05m)");
Eq(FormatWhen(nowS + 3 * 86400, now, utc), "Thu 10 Sept, 10:00 (in 3d 0h)");
Eq(FormatWhen(nowS - 60, now, utc), "09:59 (now)");
// The report: a row per seat, columns padded to line up; who is eligible
// and where the next lens lands.
const Json lim = seat(1, 116, 21, {{"state", "limited"}, {"util", 115}, {"error", "five_hour rejected, overage out_of_credits"}});
Json three = seat(3, 62, 16);
three["windows"][fable] = {{"utilization", 22}, {"resetsAt", 0}};
std::vector<std::string> lines = Lines(RenderStatusReport({lim, seat(2, 61, 18), three}, cfg, now, utc));
Eq(lines.at(0), "| seat | 5h | resets | 7d | resets | fable 7d |");
Eq(lines.at(1), "|------|------|-------------------|-----|-------------------------------|----------|");
Eq(lines.at(2), "| 1 | 116% | 10:05 (in 5 min) | 21% | Thu 10 Sept, 10:00 (in 3d 0h) | — |");
Eq(lines.at(3), "| 2 | 61% | 10:10 (in 10 min) | 18% | Thu 10 Sept, 10:00 (in 3d 0h) | — |");
Eq(lines.at(4), "| 3 | 62% | 10:15 (in 15 min) | 16% | Thu 10 Sept, 10:00 (in 3d 0h) | 22% |");
Eq(lines.at(5), "");
Eq(lines.at(6), "policy spread · ceiling 85% · handover at 90% · a lens is placed at +10% · 7d cutoff 95%");
Eq(lines.at(7), "eligible now: seat 2 (25% headroom), seat 3 (25% headroom) → next lens: seat 2");
Eq(lines.at(8), "fable lenses: seat 3 (63% fable headroom)");
Eq(lines.at(9), "limited: seat 1 until 10:05 (in 5 min)");
// A seat the probe could not read: an empty row, the reason under the table.
const std::string dead = RenderStatusReport({seat(2, 10), {{"seat", 4}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout after 20000 ms"}}}, cfg, now, utc);
Eq(dead.contains("| 4 | — | — | — | — | — |"), true);
// The board: the passes every driver on this machine has in flight on an
// account, a column only when there are any.
const std::vector<std::string> held = Lines(RenderStatusReport({seat(2, 61, 18), three}, cfg, now, utc, {{"seats", {{"k3", {{"inflight", 4}}}}}}, {{3, "k3"}}));
Eq(held.at(0), "| seat | 5h | resets | 7d | resets | fable 7d | in flight |");
Eq(held.at(3), "| 3 | 62% | 10:15 (in 15 min) | 16% | Thu 10 Sept, 10:00 (in 3d 0h) | 22% | 4 |");
Eq(dead.contains("not usable: seat 4 unknown (probe timeout after 20000 ms)"), true);
// A Fable probe refused for the Fable window needs the Haiku stand-in,
// which it then lands on: its window, and 'limited' for that window alone.
Json fableWindows = seat(2, 10).at("windows");
fableWindows[fable] = {{"utilization", 100}, {"resetsAt", nowS + 86400}};
const Json fableLimited = With(seat(2, 10), {{"state", "limited"}, {"util", 100}, {"binding", fable}, {"error", std::format("{} rejected", fable)}, {"windows", fableWindows}});
Eq(NeedsHaiku(fableLimited), true);
const Json merged = MergeFableProbe(seat(2, 10), fableLimited);
EqJson(merged.at("fable"), {{"state", "limited"}});
EqJson(merged.at("windows").at(fable), {{"utilization", 100}, {"resetsAt", nowS + 86400}});
std::string r = RenderStatusReport({merged}, cfg, now, utc);
Eq(Lines(r).at(2).contains("| limited (100%) |"), true);
Eq(r.contains("fable lenses: no seat with room on its Fable window — Fable lenses run on their fallback model"), true);
Eq(r.contains("eligible now: seat 2 (75% headroom)"), true);
// A readable Fable probe is the seat itself.
Json fullWindows = seat(3, 20, 30).at("windows");
fullWindows[fable] = {{"utilization", 44}, {"resetsAt", nowS + 86400}};
const Json full = With(seat(3, 20, 30), {{"windows", fullWindows}});
Eq(NeedsHaiku(full), false);
EqJson(MergeFableProbe(full, full).at("fable"), {{"state", "ok"}});
// A drained Fable window makes an Opus seat, never an idle one.
Json drainedWindows = seat(4, 10, 20).at("windows");
drainedWindows[fable] = {{"utilization", 98}, {"resetsAt", nowS + 86400}};
Json drained = With(seat(4, 10, 20), {{"util", 100}, {"binding", fable}, {"windows", drainedWindows}});
drained = MergeFableProbe(drained, drained);
EqJson(Json::array({drained.at("util"), drained.at("binding"), drained.at("fable")}), Json::array({20, "seven_day", {{"state", "ok"}}}));
r = RenderStatusReport({drained, full}, cfg, now, utc);
Eq(r.contains("eligible now: seat 4 (65% headroom), seat 3 (55% headroom) → next lens: seat 4"), true);
Eq(r.contains("fable lenses: seat 3 (41% fable headroom)"), true);
// Reported under the CLI's other name, it lands under the canonical key.
Json altWindows = seat(5, 10).at("windows");
altWindows["seven_day_fable"] = {{"utilization", 44}, {"resetsAt", nowS + 86400}};
Json alt = With(seat(5, 10), {{"windows", altWindows}});
alt = MergeFableProbe(alt, alt);
EqJson(alt.at("windows").at(fable), {{"utilization", 44}, {"resetsAt", nowS + 86400}});
Eq(alt.at("windows").contains("seven_day_fable"), false);
Eq(RenderStatusReport({alt}, cfg, now, utc).contains("fable lenses: seat 5 (41% fable headroom)"), true);
// A Fable probe refused for the 5-hour window: the seat is limited for
// everything, no stand-in, the Fable window still read.
Json fiveWindows = seat(2, 110).at("windows");
fiveWindows[fable] = {{"utilization", 40}, {"resetsAt", nowS + 86400}};
const Json fiveLimited = With(seat(2, 110), {{"state", "limited"}, {"util", 110}, {"binding", "five_hour"}, {"error", "five_hour rejected"}, {"windows", fiveWindows}});
Eq(NeedsHaiku(fiveLimited), false);
r = RenderStatusReport({MergeFableProbe(fiveLimited, fiveLimited)}, cfg, now, utc);
Eq(Lines(r).at(2).contains("| 110% | 10:10 (in 10 min) | 0%") && Lines(r).at(2).ends_with("| 40% |"), true);
Eq(r.contains("limited: seat 2 until 10:10 (in 10 min)"), true);
Eq(r.contains("fable lenses: seat 2"), false);
Eq(NeedsHaiku({{"seat", 1}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout"}}), true);
Eq(NeedsHaiku(seat(1, 10)), false);
// A Fable probe that could not be read: its state in the cell, the reason
// under the table.
const Json deadFable = MergeFableProbe(seat(2, 10), {{"seat", 2}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout after 20000 ms"}});
r = RenderStatusReport({deadFable}, cfg, now, utc);
Eq(Lines(r).at(2).contains("| unknown |"), true);
Eq(r.contains("fable probe: seat 2 unknown (probe timeout after 20000 ms)"), true);
const Json revoked = {{"seat", 3}, {"state", "invalid"}, {"windows", Json::object()}, {"error", "token revoked"}};
r = RenderStatusReport({MergeFableProbe(revoked, revoked)}, cfg, now, utc);
Eq(r.contains("not usable: seat 3 invalid (token revoked)"), true);
Eq(r.contains("fable probe:"), false);
// 2026-10-31: an account whose 5-hour limit is removed reports the weekly
// window alone - `no limit` in its 5h cell, the weekly window its burst
// window, ranked by it.
const Json nf = {{"seat", 3}, {"state", "ok"}, {"windows", {{"seven_day", {{"utilization", 3}, {"resetsAt", nowS + 6 * 86400}}}}}, {"util", 5}, {"binding", "seven_day"}, {"resetsAt", nowS + 6 * 86400}};
r = RenderStatusReport({seat(2, 61, 18), nf}, cfg, now, utc);
Eq(Lines(r).at(3), std::string("| 3 | no limit | — | 3% | Sun 13 Sept, 10:00 (in 6d 0h) | — |"));
Eq(Lines(r).at(6), std::string("eligible now: seat 3 (80% headroom), seat 2 (25% headroom) → next lens: seat 3"));
Eq(BurstWindow(nf.at("windows")) == "seven_day" && BurstWindow(seat(2, 61).at("windows")) == "five_hour" && !BurstWindow(Json::object()), true);
Eq(std::format("{}|{}|{}", WindowCell(nf.at("windows"), "five_hour"), WindowCell(Json::object(), "five_hour"), WindowCell(nf.at("windows"), "seven_day")), std::string("no limit|—|3%"));
// The local login is seat 0, named `local`.
r = RenderStatusReport({seat(0, 30), seat(2, 50)}, cfg, now, utc);
Eq(Lines(r).at(2).starts_with("| local | 30%"), true);
Eq(r.contains("eligible now: local (55% headroom), seat 2 (35% headroom) → next lens: local"), true);
// The pick: the pipeline's one-lens wave.
auto unknown = [](std::int32_t k) { return Json{{"seat", k}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout"}}; };
auto invalid = [](std::int32_t k) { return Json{{"seat", k}, {"state", "invalid"}, {"windows", Json::object()}, {"error", "authentication failed"}}; };
EqJson(PickNext({}, cfg), {{"kind", "stop"}, {"status", "error"}, {"reason", "no seat token configured"}});
EqJson(PickNext({invalid(1), invalid(2)}, cfg).at("reason"), "no valid seat token (seats 1, 2 rejected)");
EqJson(PickNext({unknown(3), unknown(1)}, cfg), {{"kind", "seat"}, {"seat", 1}, {"source", "round-robin"}});
EqJson(PickNext({seat(1, 5), seat(2, 1), seat(3, 0)}, cfg), {{"kind", "seat"}, {"seat", 2}, {"source", "probe"}});
EqJson(PickNext({seat(1, 5), seat(2, 1), seat(3, 0)}, With(cfg, {{"policy", "pack"}})).at("seat"), 1);
EqJson(PickNext({seat(1, 90, 10), seat(2, 80, 96), seat(3, 86)}, cfg).at("seat"), 3);
EqJson(PickNext({seat(1, 50), unknown(2)}, cfg).at("seat"), 1);
// Tokens: TOKEN1..6 by fixed name, the legacy name as seat 1, a dotenv
// file's token lines only.
const std::string text = Dyn::Join(std::vector<std::string>{"MAILOSAUR_API_KEY=abc def", "export CLAUDE_CODE_OAUTH_TOKEN1=\"one\"", "CLAUDE_CODE_OAUTH_TOKEN2='two'",
"CLAUDE_CODE_OAUTH_TOKEN3 = three ", "CLAUDE_CODE_OAUTH_TOKEN7=seven", "CLAUDE_CODE_OAUTH_TOKEN=legacy",
"# CLAUDE_CODE_OAUTH_TOKEN4=commented"}, "\n");
const std::map<std::string, std::string> parsed = SeatTokensFromEnvFile(text);
const std::map<std::string, std::string> wantParsed = {{"CLAUDE_CODE_OAUTH_TOKEN1", "one"}, {"CLAUDE_CODE_OAUTH_TOKEN2", "two"}, {"CLAUDE_CODE_OAUTH_TOKEN3", "three"}, {"CLAUDE_CODE_OAUTH_TOKEN", "legacy"}};
Eq(parsed == wantParsed, true);
std::vector<std::int32_t> ids;
for (const auto& [n, token] : SeatTokensFromEnv(parsed)) ids.push_back(n);
Eq(ids == std::vector<std::int32_t>{1, 2, 3}, true);
using Tokens = std::vector<std::pair<std::int32_t, std::string>>;
Eq(SeatTokensFromEnv({{"CLAUDE_CODE_OAUTH_TOKEN1", "a"}, {"CLAUDE_CODE_OAUTH_TOKEN2", " "}, {"CLAUDE_CODE_OAUTH_TOKEN3", "c"}, {"CLAUDE_CODE_OAUTH_TOKEN7", "x"},
{"CLAUDE_CODE_OAUTH_TOKENX", "y"}, {"CLAUDE_CODE_OAUTH_TOKEN", "legacy"}}) == Tokens{{1, "a"}, {3, "c"}}, true);
Eq(SeatTokensFromEnv({{"CLAUDE_CODE_OAUTH_TOKEN", "legacy"}, {"CLAUDE_CODE_OAUTH_TOKEN2", "b"}}) == Tokens{{1, "legacy"}, {2, "b"}}, true);
Eq(SeatTokensFromEnv({}).empty(), true);
// Config: repo variables override the defaults, garbage does not.
EqJson(ConfigFromEnv({}), {{"maxUtil", 85}, {"lensCostPct", 10}, {"waitMaxMin", 15}, {"policy", "spread"}, {"handoverPct", 90}});
EqJson(ConfigFromEnv({{"REVIEW_GATES_SEAT_MAX_UTIL", "70"}, {"REVIEW_GATES_LENS_COST_PCT", "abc"}, {"REVIEW_GATES_SEAT_WAIT_MIN", "0"}, {"REVIEW_GATES_SEAT_HANDOVER_PCT", "80"},
{"REVIEW_GATES_SEAT_POLICY", "pack"}}),
{{"maxUtil", 70}, {"lensCostPct", 10}, {"waitMaxMin", 0}, {"policy", "pack"}, {"handoverPct", 80}});
EqJson(ConfigFromEnv({{"REVIEW_GATES_SEAT_POLICY", "weird"}}).at("policy"), "spread");
// The stream: captured from claude 2.1.263, values shortened.
const std::string init = R"({"type":"system","subtype":"init","model":"claude-haiku-4-5-20251001","claude_code_version":"2.1.263"})";
const std::string event = R"({"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1788789600,"rateLimitType":"five_hour",)"
R"("overageStatus":"rejected","overageDisabledReason":"out_of_credits","isUsingOverage":false,"unifiedWindows":{)"
R"("five_hour":{"utilization":0.05,"resetsAt":1788789600},"seven_day":{"utilization":0.01,"resetsAt":1789038000}}},)"
R"("uuid":"u","session_id":"400f3eb9-4732-42f1-a257-b84d1da95164"})";
const std::string assistant = R"({"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"ok"}]}})";
const std::string result = R"({"type":"result","subtype":"success","is_error":false,"structured_output":{"ok":true},"result":"ok","modelUsage":{"claude-haiku-4-5-20251001":{"inputTokens":134}}})";
for (const std::vector<std::string>& order : {std::vector<std::string>{init, event, assistant, result}, std::vector<std::string>{init, assistant, event, result},
std::vector<std::string>{init, assistant, result, event}}) {
const Json p = ParseStream(std::format("{}\n", Dyn::Join(order, "\n")));
EqJson(p.at("result").at("structured_output"), {{"ok", true}});
EqJson(p.at("sessionId"), "400f3eb9-4732-42f1-a257-b84d1da95164");
const Json& rl = p.at("rateLimit");
EqJson(Json::array({rl.at("status"), rl.at("rateLimitType"), rl.at("resetsAt"), rl.at("overageReason")}), Json::array({"allowed", "five_hour", 1788789600, "out_of_credits"}));
EqJson(rl.at("windows").at("five_hour"), {{"utilization", 5}, {"resetsAt", 1788789600}});
EqJson(rl.at("windows").at("seven_day"), {{"utilization", 1}, {"resetsAt", 1789038000}});
}
EqJson(ParseStream(Dyn::Join(std::vector<std::string>{"garbage", event, "more garbage", result}, "\n")).at("result").at("structured_output"), {{"ok", true}});
EqJson(ParseStream("{\n \"type\": \"result\",\n \"subtype\": \"success\"\n}").at("result"), nullptr);
EqJson(ParseStream("").at("rateLimit"), nullptr);
EqJson(Json::array({Pct(1.16), Pct(0.05), Pct(0.625), Pct("x"), Pct(-1)}), Json::array({116, 5, 62.5, 0, 0}));
auto pair = [](const std::pair<Json, std::string>& p) { return Json::array({p.first, p.second}); };
EqJson(pair(SeatUtil({{"five_hour", {{"utilization", 61}, {"resetsAt", 1}}}, {"seven_day", {{"utilization", 18}, {"resetsAt", 2}}}})), Json::array({60, "five_hour"}));
EqJson(pair(SeatUtil({{"five_hour", {{"utilization", 12}, {"resetsAt", 1}}}, {"seven_day_fable", {{"utilization", 97}, {"resetsAt", 2}}}})), Json::array({95, "seven_day_fable"}));
Eq(IsEligible(seat(1, 10, 96), With(cfg, {{"maxUtil", 100}})), false);
Eq(IsEligible(seat(1, 10, 94), With(cfg, {{"maxUtil", 100}})), true);
Eq(IsEligible(seat(1, 10, 94), cfg), false);
// Redaction: the seat's own token, token shapes and addresses; auth
// failures by status or by text.
const std::vector<std::string> secret = {"lit-secret"};
Eq(ClipReason("token sk-ant-oat01-abcdefghijklmnopqrstuvwxyz0123 and jorijn@example.com x\n y", secret), "token [redacted:token] and [redacted:email] x y");
Eq(ClipReason("bad lit-secret here", secret), "bad [redacted:token] here");
Eq(LooksLikeAuthFailure({{"api_error_status", 401}}, ""), true);
Eq(LooksLikeAuthFailure(nullptr, "Invalid API key · Please run /login"), true);
Eq(LooksLikeAuthFailure({{"result", "ok"}}, ""), false);
// find_claude_bin: CLAUDE_BIN, then PATH, then the newest editor-bundled
// binary (numerically), else the bare name.
const fs::path home = fs::temp_directory_path() / std::format("seats-home-{}", Platform::PublishedPid());
fs::remove_all(home);
fs::create_directories(home);
Eq(FindClaudeBin({{"CLAUDE_BIN", "/x/claude"}, {"PATH", ""}}, home.string()), "/x/claude");
const fs::path binDir = home / "bin";
fs::create_directories(binDir);
const fs::path fake = binDir / "claude";
std::ofstream(fake).close();
fs::permissions(fake, fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec | fs::perms::others_read | fs::perms::others_exec);
Eq(FindClaudeBin({{"PATH", std::format("/nowhere:{}", binDir.string())}}, home.string()), fake.string());
for (const std::string_view v : {"2.1.100-linux-x64", "2.1.99-linux-x64", "2.1.263-linux-x64"}) {
const fs::path d = home / ".vscode-oss/extensions" / std::format("anthropic.claude-code-{}", v) / "resources/native-binary";
fs::create_directories(d);
std::ofstream(d / "claude").close();
}
Eq(FindClaudeBin({{"PATH", "/nowhere"}}, home.string()), (home / ".vscode-oss/extensions/anthropic.claude-code-2.1.263-linux-x64/resources/native-binary/claude").string());
Eq(FindClaudeBin({{"PATH", "/nowhere"}}, (home / "empty").string()), "claude");
fs::remove_all(home);
std::println("seats: {} checks{}", N, Failures ? std::format(", {} failed", Failures) : std::string(" ok"));
return Failures ? 1 : 0;
}