Crafter.Review/implementations/Crafter.Review-SeatPool.cpp

1896 lines
99 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
module Crafter.Review:SeatPool_impl;
import :SeatPool;
import :Json;
import :Text;
import :Crypto;
import :Platform;
import :Http;
import :Seats;
import std;
namespace fs = std::filesystem;
namespace Crafter::Review::SeatPool {
const std::string_view ResumePrompt =
"The previous attempt of this pass was interrupted by a limit on the account it ran on and continues here on "
"another account with the same transcript. Do not repeat work already done: continue from where it stopped, "
"finish the pass, and return the structured output exactly as the instructions require.\n";
namespace {
using Log = std::function<void(const std::string&)>;
// A number as the files and the probes hold it, as a float.
double AsDouble(const Json& value) {
if (value.is_number_float()) return value.get<double>();
if (value.is_boolean()) return value.get<bool>() ? 1.0 : 0.0;
if (value.is_number_unsigned()) return static_cast<double>(value.get<std::uint64_t>());
if (value.is_number_integer()) return static_cast<double>(value.get<std::int64_t>());
if (value.is_string()) {
if (const std::optional<double> parsed = Dyn::ParseFloat(value.get_ref<const std::string&>())) return *parsed;
throw Dyn::Error("ValueError", Dyn::FloatError(value.get_ref<const std::string&>()));
}
throw Dyn::Error("TypeError", std::format("float() argument must be a string or a real number, not '{}'", Dyn::TypeName(value)));
}
// The value as a float, 0 when it is falsy.
double Num(const Json& value) {
return Dyn::Truthy(value) ? AsDouble(value) : 0.0;
}
// A float truncated toward zero, saturating at the ends of int64.
std::int64_t Int(double value) {
if (std::isinf(value)) throw Dyn::Error("OverflowError", "cannot convert float infinity to integer");
if (std::isnan(value)) throw Dyn::Error("ValueError", "cannot convert float NaN to integer");
if (value >= 9.2e18) return std::numeric_limits<std::int64_t>::max();
if (value <= -9.2e18) return std::numeric_limits<std::int64_t>::min();
return static_cast<std::int64_t>(value);
}
// `now or time.time()`
double NowOr(std::optional<double> now) {
return now && *now != 0 ? *now : Platform::Now();
}
// f'{value:g}'
std::string G(double value) {
return std::format("{:g}", value);
}
std::string Clock(double epoch) {
return Platform::LocalTime("%H:%M:%S", epoch);
}
Json ZeroForeign() {
return {{"count", 0}, {"cost5h", 0.0}, {"cost7d", 0.0}, {"costUsd", 0.0}};
}
std::string Kind(const Json& seat) {
return Dyn::Str(Dyn::At(seat, "kind"));
}
std::string Id(const Json& seat) {
return Dyn::Str(Dyn::At(seat, "id"));
}
std::int64_t Running(const Json& seat) {
return Int(AsDouble(Dyn::At(seat, "running")));
}
const std::string& EnvOf(const std::map<std::string, std::string>& env, const std::string& key) {
static const std::string none;
const auto found = env.find(key);
return found == env.end() ? none : found->second;
}
bool IsOpenRouter(const Json& seat) {
return Kind(seat) == "openrouter";
}
double Budget(const Json& s, const Json& a) {
return AsDouble(Dyn::At(a, IsOpenRouter(s) ? "orBudgetUsd" : "budgetUsd"));
}
const Json& LensCostValue(const Json& s, const Json& a) {
return Dyn::At(a, IsOpenRouter(s) ? "orLensCostUsd" : "lensCostUsd");
}
double LensCost(const Json& s, const Json& a) {
return AsDouble(LensCostValue(s, a));
}
std::int64_t Concurrency(const Json& s, const Json& a) {
return Int(AsDouble(Dyn::At(a, IsOpenRouter(s) ? "orConcurrency" : "concurrency")));
}
// The windows a seat's utilisation is read from: all but the Fable
// one, or all when that leaves none.
void Reutil(Json& s) {
Json own = Json::object();
const Json& windows = s.at("windows");
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (!Seats::IsFableWindow(it.key())) own[it.key()] = it.value();
}
const auto [util, binding] = Seats::SeatUtil(own.empty() ? windows : own);
s["util"] = util;
s["binding"] = binding;
}
bool LongOk(const Json& s) {
const Json& windows = Dyn::At(s, "windows");
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (it.key() == "five_hour" || Seats::IsFableWindow(it.key())) continue;
if (Num(Dyn::Get(it.value(), "utilization")) >= Seats::LongWindowCutoff) return false;
}
return true;
}
bool ApiRoom(const Json& s, const Json& a) {
return AsDouble(s.at("spentUsd")) + static_cast<double>(Running(s) + 1) * LensCost(s, a) + AsDouble(Foreign(s).at("costUsd")) <= Budget(s, a);
}
const Json& FiveHour(const Json& s) {
return Dyn::Or(Dyn::Get(Dyn::At(s, "windows"), "five_hour"), Dyn::EmptyDict());
}
// ik.split('/', 1)[0]: the driver an in-flight entry belongs to.
std::string Owner(std::string_view inflightKey) {
return std::string(inflightKey.substr(0, inflightKey.find('/')));
}
// key.split(':')[0]
std::string PidOf(std::string_view key) {
return std::string(key.substr(0, key.find(':')));
}
// Board._key
std::string KeyOf(const Json& seat) {
const Json& key = Dyn::Get(seat, "boardKey");
return Dyn::Truthy(key) ? Dyn::Str(key) : SeatKey(Kind(seat), Dyn::StrOr(Dyn::Get(seat, "secret"), ""));
}
// Per-token list prices when the catalogue cannot be fetched
// (OpenRouter, 2026-10-12).
const Json& BuiltInPrices() {
static const Json prices = {{"xiaomi/mimo-v2.6-pro", {{"prompt", 0.435e-6}, {"completion", 0.87e-6}, {"cacheRead", 0.0036e-6}, {"cacheWrite", nullptr}}}};
return prices;
}
// model -> prices, filled by FetchOpenRouterPrices.
std::map<std::string, Json> Prices;
std::mutex PricesLock;
void WriteAll(std::int32_t fd, std::string_view text) {
std::size_t written = 0;
while (written < text.size()) {
const ssize_t step = write(fd, text.data() + written, text.size() - written);
if (step < 0) {
if (errno == EINTR) continue;
const std::int32_t failure = errno;
close(fd);
throw std::runtime_error(Dyn::ErrnoText(failure));
}
written += static_cast<std::size_t>(step);
}
close(fd);
}
// shutil.copyfile: the bytes only, the destination created as open()
// creates it.
void CopyContents(const fs::path& from, const fs::path& to) {
const std::optional<std::string> bytes = Platform::ReadFile(from);
if (!bytes) throw std::runtime_error(std::format("{}: {}", Dyn::ErrnoText(ENOENT), Dyn::Repr(from.string())));
const std::int32_t fd = open(to.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
if (fd < 0) throw std::runtime_error(std::format("{}: {}", Dyn::ErrnoText(errno), Dyn::Repr(to.string())));
WriteAll(fd, *bytes);
}
}
bool IsPaid(const Json& seat) {
return std::ranges::contains(Paid, Kind(seat));
}
Json ApiConfig(const std::map<std::string, std::string>& env) {
auto num = [&](const std::string& key, double fallback) {
const std::optional<double> v = Dyn::ParseFloat(EnvOf(env, key));
return v && *v >= 0 ? *v : fallback;
};
return {
{"budgetUsd", num("REVIEW_GATES_API_BUDGET_USD", 200.0)},
{"lensCostUsd", num("REVIEW_GATES_API_LENS_COST_USD", 1.0)},
{"concurrency", Int(num("REVIEW_GATES_API_CONCURRENCY", 8))},
// A day past a month's end is its last day (CycleStart).
{"cycleDay", std::max<std::int64_t>(1, std::min<std::int64_t>(31, Int(num("REVIEW_GATES_API_CYCLE_DAY", 1))))},
{"order", EnvOf(env, "REVIEW_GATES_SEAT_ORDER") == "oauth-first" ? "oauth-first" : "api-first"},
// OpenRouter seats: a prepaid key, cheap models - a smaller reserve
// per running process.
{"orBudgetUsd", num("REVIEW_GATES_OPENROUTER_BUDGET_USD", 100.0)},
{"orLensCostUsd", num("REVIEW_GATES_OPENROUTER_LENS_COST_USD", 0.1)},
{"orConcurrency", Int(num("REVIEW_GATES_OPENROUTER_CONCURRENCY", 16))},
};
}
Json Foreign(const Json& seat) {
const Json& foreign = Dyn::Get(seat, "foreign");
return Dyn::Truthy(foreign) ? foreign : ZeroForeign();
}
fs::path LedgerPath(const std::optional<fs::path>& scratchRoot) {
fs::path root;
if (scratchRoot && !scratchRoot->empty()) {
root = *scratchRoot;
} else if (const std::optional<std::string> configured = Platform::GetEnv("REVIEW_GATES_SCRATCH"); configured && !configured->empty()) {
root = *configured;
} else {
root = fs::path(Platform::Home()) / ".cache" / "review-gates";
}
return root / "seat-ledger.json";
}
namespace {
std::chrono::year_month_day LocalDate(double now) {
using namespace std::chrono;
const std::string today = Platform::LocalTime("%Y-%m-%d", now);
return {year(std::stoi(today.substr(0, 4))), month(static_cast<unsigned>(std::stoi(today.substr(5, 2)))), day(static_cast<unsigned>(std::stoi(today.substr(8, 2))))};
}
std::chrono::year_month_day ParseIso(std::string_view iso) {
using namespace std::chrono;
const std::string text(iso);
return {year(std::stoi(text.substr(0, 4))), month(static_cast<unsigned>(std::stoi(text.substr(5, 2)))), day(static_cast<unsigned>(std::stoi(text.substr(8, 2))))};
}
std::string Iso(const std::chrono::year_month_day& d) {
return std::format("{:04d}-{:02d}-{:02d}", static_cast<std::int32_t>(d.year()), static_cast<std::uint32_t>(d.month()), static_cast<std::uint32_t>(d.day()));
}
// A ledger stamp: local time with its UTC offset,
// 2026-10-25T02:00:05+01:00, so any driver orders it by instant across
// the hour that repeats when DST ends, or from another zone.
std::string Stamp(double at) {
std::string text = Platform::LocalTime("%Y-%m-%dT%H:%M:%S%z", at);
if (text.size() == 24) text.insert(22, ":");
return text;
}
// A stamp's instant, epoch seconds: by its offset, else (a stamp
// written before 1.1.0) as local time; none when it cannot be read.
std::optional<double> Instant(std::string_view stamp) {
auto number = [&](std::size_t from, std::size_t width) -> std::optional<std::int32_t> {
if (from + width > stamp.size()) return std::nullopt;
std::int32_t value = 0;
for (std::size_t i = from; i < from + width; ++i) {
if (stamp[i] < '0' || stamp[i] > '9') return std::nullopt;
value = value * 10 + (stamp[i] - '0');
}
return value;
};
if (stamp.size() < 19 || stamp[4] != '-' || stamp[7] != '-' || stamp[10] != 'T' || stamp[13] != ':' || stamp[16] != ':') return std::nullopt;
const std::optional<std::int32_t> y = number(0, 4), mo = number(5, 2), d = number(8, 2), h = number(11, 2), mi = number(14, 2), s = number(17, 2);
if (!y || !mo || !d || !h || !mi || !s) return std::nullopt;
const double clock = *h * 3600.0 + *mi * 60.0 + *s;
if (stamp.size() == 25 && (stamp[19] == '+' || stamp[19] == '-') && stamp[22] == ':') {
const std::optional<std::int32_t> oh = number(20, 2), om = number(23, 2);
const std::chrono::year_month_day date {std::chrono::year(*y), std::chrono::month(static_cast<unsigned>(*mo)), std::chrono::day(static_cast<unsigned>(*d))};
if (!oh || !om || !date.ok()) return std::nullopt;
const double offset = (stamp[19] == '-' ? -1.0 : 1.0) * (*oh * 3600.0 + *om * 60.0);
return static_cast<double>(std::chrono::sys_days(date).time_since_epoch().count()) * 86400.0 + clock - offset;
}
if (stamp.size() != 19) return std::nullopt;
std::tm local {};
local.tm_year = *y - 1900;
local.tm_mon = *mo - 1;
local.tm_mday = *d;
local.tm_hour = *h;
local.tm_min = *mi;
local.tm_sec = *s;
local.tm_isdst = -1;
const std::time_t seconds = std::mktime(&local);
if (seconds == static_cast<std::time_t>(-1)) return std::nullopt;
return static_cast<double>(seconds);
}
// That day of the month, a day past its end its last day.
std::chrono::year_month_day OnDay(const std::chrono::year_month& ym, std::int32_t day) {
using namespace std::chrono;
const auto last = static_cast<std::int32_t>(static_cast<unsigned>(year_month_day_last(ym.year(), month_day_last(ym.month())).day()));
return {ym.year(), ym.month(), std::chrono::day(static_cast<unsigned>(std::clamp(day, 1, last)))};
}
}
std::string CycleStart(double now, std::int32_t day) {
using namespace std::chrono;
const year_month_day date = LocalDate(now);
const year_month ym(date.year(), date.month());
const year_month_day thisMonth = OnDay(ym, day);
return Iso(date >= thisMonth ? thisMonth : OnDay(ym - months(1), day));
}
std::string NextGrant(double now, std::int32_t day, const Json& outSince) {
using namespace std::chrono;
const std::string cycle = CycleStart(now, day);
if (Dyn::Truthy(outSince) && Dyn::Slice(Dyn::Str(outSince), 0, 10) < cycle) return cycle;
const year_month_day start = ParseIso(cycle);
return Iso(OnDay(year_month(start.year(), start.month()) + months(1), day));
}
std::pair<bool, std::string> LedgerVerdict(const Json& entry, double budget) {
const Json& outSince = Dyn::Get(entry, "outSince");
if (Dyn::Truthy(outSince)) return {true, std::format("ledger: {} since {}", Dyn::StrOr(Dyn::Get(entry, "outReason"), "out"), Dyn::Slice(Dyn::Str(outSince), 0, 16))};
const double spent = Num(Dyn::Get(entry, "spentUsd"));
if (spent >= budget) return {true, std::format("ledger: ${:.2f} spent this cycle, budget ${}", spent, G(budget))};
return {false, std::string()};
}
Json LedgerOut(const Json& entry, double budget) {
if (!LedgerVerdict(entry, budget).first) return nullptr;
return Dyn::Truthy(Dyn::Get(entry, "outSince")) ? "credit" : "budget";
}
// ---- the ledger -------------------------------------------------------------------
Ledger::Ledger(fs::path path, Json apiConfig, Json data, std::map<std::string, std::int32_t> days)
: path(std::move(path)), apiConfig(std::move(apiConfig)), data(Dyn::Truthy(data) ? std::move(data) : Json::object()), days(std::move(days)) {}
Ledger Ledger::Load(const fs::path& path, const Json& apiConfig, std::map<std::string, std::int32_t> days) {
Json data;
try {
data = Dyn::LoadJsonFile(path);
} catch (const std::exception&) {
data = Json::object();
}
return Ledger(path, apiConfig, data.is_object() ? data : Json::object(), std::move(days));
}
std::int32_t Ledger::Day(const std::string& seatId) const {
const auto found = days.find(seatId);
if (found != days.end() && found->second > 0) return found->second;
return static_cast<std::int32_t>(Int(AsDouble(Dyn::At(apiConfig, "cycleDay"))));
}
void Ledger::SetDay(const std::string& seatId, std::int32_t day) {
days[seatId] = day;
}
Json& Ledger::Entry(const std::string& seatId, std::optional<double> now) {
const std::string cycle = CycleStart(NowOr(now), Day(seatId));
const Json& e = Dyn::Get(data, seatId);
if (!Dyn::Truthy(e)) {
data[seatId] = {{"cycle", cycle}, {"spentUsd", 0.0}, {"procs", 0}, {"outSince", nullptr}, {"outReason", nullptr}};
} else if (cycle > Dyn::StrOr(Dyn::Get(e, "cycle"), "")) {
// Only a newer cycle starts over: an entry of a later one (another
// driver's cycle day, or a grant seen early) is not wiped. A
// credit-out carries: the cycle's day is a configured date, the
// credit error was observed (2026-10-17: the calendar cycle held
// api3 out for the first week of its new credits; an OpenRouter
// key's turn puts nothing on it), and only a probe that sees the
// key answer again lifts it (Reconcile). When the key was last seen
// answering again carries too: Reconcile records no credit error
// read before it.
Json next = {{"cycle", cycle}, {"spentUsd", 0.0}, {"procs", 0}, {"outSince", Dyn::Get(e, "outSince")}, {"outReason", Dyn::Get(e, "outReason")}};
if (Dyn::Truthy(Dyn::Get(e, "grantSeen"))) next["grantSeen"] = Dyn::Get(e, "grantSeen");
data[seatId] = std::move(next);
}
return data[seatId];
}
Json Ledger::Update(const std::string& seatId, const std::function<bool(Json&)>& change, std::optional<double> now) {
// The file is the record: re-read under its lock, this process only
// ever adds to it (2026-10-15: each driver saved the copy it had loaded
// and the last writer won - api4 reached the credit error with $157.54
// recorded of the $199.66 its processes had cost).
Json entry;
Platform::LockedUpdate(path, [&](Json& file) {
// The file wins where both have a seat.
Json merged = data;
for (auto it = file.begin(); it != file.end(); ++it) merged[it.key()] = it.value();
data = std::move(merged);
Json& e = Entry(seatId, now);
const bool write = change(e);
entry = e;
if (write) file = data;
return write;
}, data, 0600, {});
return entry;
}
Json Ledger::Add(const std::string& seatId, double usd, std::optional<double> now) {
return Update(seatId, [&](Json& e) {
e["spentUsd"] = Dyn::Round(AsDouble(Dyn::At(e, "spentUsd")) + usd, 4);
e["procs"] = Dyn::Add(Dyn::At(e, "procs"), Json(1));
return true;
}, now);
}
Json Ledger::MarkOut(const std::string& seatId, std::string_view reason, std::optional<double> now) {
return Update(seatId, [&](Json& e) {
e["outSince"] = Stamp(NowOr(now));
e["outReason"] = Dyn::Slice(reason, 0, 160);
return true;
}, now);
}
Json Ledger::GrantEntry(const std::string& seatId, double at) const {
using namespace std::chrono;
const std::string cycle = CycleStart(at, Day(seatId));
const std::string weekAgo = Iso(year_month_day(sys_days(LocalDate(at)) - std::chrono::days(7)));
const std::string start = cycle >= weekAgo ? cycle : Iso(LocalDate(at));
return {{"cycle", start}, {"spentUsd", 0.0}, {"procs", 0}, {"outSince", nullptr}, {"outReason", nullptr}, {"grantSeen", Stamp(at)}};
}
std::string Ledger::Reconcile(const std::string& seatId, const Json& state, double probedAt, std::optional<double> now) {
if (state != "ok" && state != "out") return {};
const double at = NowOr(now);
// A mark made before the probe started, by instant to the second the
// stamps hold; one made since may postdate what the probe read, and
// one that cannot be read counts as made since.
const double started = std::floor(probedAt);
auto before = [&](const Json& stamp) {
const std::optional<double> instant = Instant(Dyn::Str(stamp));
return instant && *instant < started;
};
const Json fresh = GrantEntry(seatId, at);
std::string event;
// Decided on the file's entry under the lock, not on this process's
// copy: another driver may have recorded the grant and spent on it,
// or the credit error, since this copy was read.
// An OpenRouter key (or<n>) is prepaid: its lift is no grant, no new
// cycle - the mark goes, the cycle's spend stays.
const bool openRouter = seatId.starts_with("or");
Update(seatId, [&](Json& e) {
const Json& outSince = Dyn::Get(e, "outSince");
if (state == "ok") {
if (!Dyn::Truthy(outSince) || !before(outSince)) return false;
if (openRouter) {
e["outSince"] = nullptr;
e["outReason"] = nullptr;
e["grantSeen"] = Stamp(at);
event = "credit-back";
} else {
e = fresh;
event = "new-grant";
}
return true;
}
const Json& seen = Dyn::Get(e, "grantSeen");
if (Dyn::Truthy(outSince) || (Dyn::Truthy(seen) && !before(seen))) return false;
e["outSince"] = Stamp(at);
e["outReason"] = openRouter ? "key limit reached" : "credit balance too low";
event = "credit-gone";
return true;
}, at);
return event;
}
void Ledger::Save() {
try {
Platform::WriteAtomic(path, DumpFile(data), 0600);
} catch (const std::exception&) {
// A record, never a dependency.
}
}
const Json& Ledger::Data() const {
return data;
}
const Json& Ledger::ApiConfig() const {
return apiConfig;
}
const fs::path& Ledger::Path() const {
return path;
}
// ---- the seats ---------------------------------------------------------------------
Json NewSeat(const std::string& id, const std::string& kind, const std::string& secret) {
return {
{"id", id},
{"kind", kind},
{"secret", secret},
{"state", "unknown"},
{"windows", Json::object()},
{"util", nullptr},
{"binding", ""},
{"resetsAt", nullptr},
{"pauseUntil", 0.0},
{"running", 0},
{"procs", 0},
{"spentUsd", 0.0},
{"error", nullptr},
{"probedWith", nullptr},
{"foreign", ZeroForeign()},
// The board's view of the seat.
{"boardKey", SeatKey(kind, secret)},
{"stateFrom", nullptr},
};
}
feat(seats): the env file's settings count, and a machine's own seats file ApiConfig and PoolConfig read the seat order, the budgets and the concurrency from the process environment alone, so a `.env` that said REVIEW_GATES_SEAT_ORDER=oauth-first ordered nothing: the Python driver reads those lines from the file under the process environment (config_env, 2026-10-17: a setting lives beside the seats it orders). SeatPool::SettingsOf does the same - the file's placement settings under the process environment, in a map handed to the placement readers alone (ApiConfig, PoolConfig, Seats::ConfigFromEnv) - and leaves the process environment untouched, so no other reader sees a line of the file: a path's or a binary's line reaches nothing, and two drivers reading one .env keep one board and one ledger. The placement settings are one list, SeatPool::PlacementSettings - the fifteen names those readers read - for the map, the run's log and what makes a working directory's .env a seats file; a REVIEW_GATES_* line of any other name stays out of the map and is named, by the run's log and on `seats`'s stderr. One dotenv grammar serves the tokens and the settings (Seats::DotenvLines). `run` and `seats` resolve the file from their own --env-file (SeatPool::ResolveSeatsFile): the --env-file, else $SEATS_ENV_FILE, else the working directory's .env - the operator's, as the Python driver and `seats` read it, not the repository under review's - when it is a file holding a seat or a placement setting, each with a value, or one that cannot be read as UTF-8 text (its error is then the command's) - a readable application .env, its Vite or Supabase lines, one of blank placeholder lines or one that is no file is passed over, and the run says so - else the machine's own $XDG_CONFIG_HOME/crafter-review/env, else ~/.config/crafter-review/env: one file for every repository on a machine, a repository's seats .env winning over it. Each file is read once and the settings and the tokens are taken from that read (SeatsFile::Text), so a file that can be read only once - a pipe, `--env-file <(gpg -d seats.env.gpg)`, a FIFO - gives its settings and its seats alike, and a failed read raises its own error where the tokens are laid over the environment: SeatPool::WithFileTokens, for `run` and `seats` alike, and LoadSeats takes the merged environment and no longer reads a file by path. An empty `seats --env-file` means no file now, as it did for `run`, and the `seats` report names the side that runs first (it said "api seats run first" whatever the order). Runs, Commands and KernelModes, the tests that run `run` and `seats`, point XDG_CONFIG_HOME, and HOST_XDG_CONFIG_HOME for the Flatpak, at a path of their own that nothing creates, so a global file stays out of them; the grammar, the settings and their precedence, the lookup and the one read have their tests, a run with `--env` (a prefix) and with a repeated --env-file, and a `seats` with `--env`, take tokens and settings from the one file, an application's .env and a venv are passed over, a FIFO is read once, no line of the file reaches `stop`, every listed placement setting moves a reader, another REVIEW_GATES_* line is named and a .env of blank lines passed over, and the README, the environment table and the procedure say the new rule.
2026-11-01 01:51:48 +01:00
namespace {
// The one read of a seats file: its text, or the read's error for
// Text() to raise where a command loads its tokens.
void ReadOnce(SeatsFile& out, const fs::path& file) {
out.path = file;
try {
out.text = Dyn::ReadText(file);
} catch (const Dyn::Error&) {
out.error = std::current_exception();
}
}
feat(seats): the env file's settings count, and a machine's own seats file ApiConfig and PoolConfig read the seat order, the budgets and the concurrency from the process environment alone, so a `.env` that said REVIEW_GATES_SEAT_ORDER=oauth-first ordered nothing: the Python driver reads those lines from the file under the process environment (config_env, 2026-10-17: a setting lives beside the seats it orders). SeatPool::SettingsOf does the same - the file's placement settings under the process environment, in a map handed to the placement readers alone (ApiConfig, PoolConfig, Seats::ConfigFromEnv) - and leaves the process environment untouched, so no other reader sees a line of the file: a path's or a binary's line reaches nothing, and two drivers reading one .env keep one board and one ledger. The placement settings are one list, SeatPool::PlacementSettings - the fifteen names those readers read - for the map, the run's log and what makes a working directory's .env a seats file; a REVIEW_GATES_* line of any other name stays out of the map and is named, by the run's log and on `seats`'s stderr. One dotenv grammar serves the tokens and the settings (Seats::DotenvLines). `run` and `seats` resolve the file from their own --env-file (SeatPool::ResolveSeatsFile): the --env-file, else $SEATS_ENV_FILE, else the working directory's .env - the operator's, as the Python driver and `seats` read it, not the repository under review's - when it is a file holding a seat or a placement setting, each with a value, or one that cannot be read as UTF-8 text (its error is then the command's) - a readable application .env, its Vite or Supabase lines, one of blank placeholder lines or one that is no file is passed over, and the run says so - else the machine's own $XDG_CONFIG_HOME/crafter-review/env, else ~/.config/crafter-review/env: one file for every repository on a machine, a repository's seats .env winning over it. Each file is read once and the settings and the tokens are taken from that read (SeatsFile::Text), so a file that can be read only once - a pipe, `--env-file <(gpg -d seats.env.gpg)`, a FIFO - gives its settings and its seats alike, and a failed read raises its own error where the tokens are laid over the environment: SeatPool::WithFileTokens, for `run` and `seats` alike, and LoadSeats takes the merged environment and no longer reads a file by path. An empty `seats --env-file` means no file now, as it did for `run`, and the `seats` report names the side that runs first (it said "api seats run first" whatever the order). Runs, Commands and KernelModes, the tests that run `run` and `seats`, point XDG_CONFIG_HOME, and HOST_XDG_CONFIG_HOME for the Flatpak, at a path of their own that nothing creates, so a global file stays out of them; the grammar, the settings and their precedence, the lookup and the one read have their tests, a run with `--env` (a prefix) and with a repeated --env-file, and a `seats` with `--env`, take tokens and settings from the one file, an application's .env and a venv are passed over, a FIFO is read once, no line of the file reaches `stop`, every listed placement setting moves a reader, another REVIEW_GATES_* line is named and a .env of blank lines passed over, and the README, the environment table and the procedure say the new rule.
2026-11-01 01:51:48 +01:00
}
std::optional<fs::path> GlobalEnvFile() {
fs::path dir;
if (const std::optional<std::string> xdg = Platform::GetEnv("XDG_CONFIG_HOME"); xdg && !xdg->empty()) {
dir = fs::path(*xdg);
} else {
dir = fs::path(Platform::Home()) / ".config";
}
const fs::path file = dir / "crafter-review" / "env";
std::error_code error;
if (fs::is_regular_file(file, error)) return file;
return std::nullopt;
}
const std::string& SeatsFile::Text() const {
if (error) std::rethrow_exception(error);
return text;
}
namespace {
bool IsPlacementSetting(std::string_view name) {
return std::ranges::contains(PlacementSettings, name);
}
// A seat as the seat readers count one (a token with a value), or a
// placement setting with a value: a blank line is neither.
bool HoldsSeatOrSetting(const std::string& text) {
const std::map<std::string, std::string> tokens = Seats::SeatTokensFromEnvFile(text);
if (!Seats::SeatTokensFromEnv(tokens).empty() || !Seats::ApiKeysFromEnv(tokens).empty() || !Seats::OpenRouterKeysFromEnv(tokens).empty()) return true;
return std::ranges::any_of(Seats::SettingsFromEnvFile(text), [](const auto& entry) { return IsPlacementSetting(entry.first) && !Dyn::Strip(entry.second).empty(); });
}
}
SeatsFile ResolveSeatsFile(const std::optional<std::string>& given, const fs::path& cwd) {
SeatsFile out;
if (given) {
if (!given->empty()) ReadOnce(out, *given);
return out;
}
if (const std::optional<std::string> named = Platform::GetEnv("SEATS_ENV_FILE"); named && !named->empty()) {
ReadOnce(out, *named);
return out;
}
// The working directory's .env is the seats file when it is a file
// holding a seat or a placement setting, each with a value (a blank
// placeholder line is neither), or one that cannot be read as UTF-8
// text (its error is then the command's to report). A
// repository's application config (its Vite or Supabase lines), or a
// .env that is no file (a Python venv), is passed over for the
// machine's own file (2026-11-01, the self-review's rounds 9 and 10).
std::error_code error;
if (fs::exists(cwd / ".env", error)) {
if (fs::is_regular_file(cwd / ".env", error)) {
ReadOnce(out, cwd / ".env");
if (out.error || HoldsSeatOrSetting(out.text)) return out;
out = SeatsFile{};
}
out.passedOver = cwd / ".env";
}
if (const std::optional<fs::path> global = GlobalEnvFile()) ReadOnce(out, *global);
return out;
}
Settings SettingsOf(const SeatsFile& file) {
Settings out {Platform::Environment(), {}, {}};
for (const auto& [name, value] : Seats::SettingsFromEnvFile(file.text)) {
if (!IsPlacementSetting(name)) out.ignored.push_back(name);
else if (!Dyn::Strip(value).empty() && out.env.try_emplace(name, value).second) out.fromFile.push_back(name);
}
return out;
}
std::map<std::string, std::string> WithFileTokens(std::map<std::string, std::string> env, const SeatsFile& file) {
for (const auto& [key, value] : Seats::SeatTokensFromEnvFile(file.Text())) env[key] = value;
return env;
}
std::vector<Json> LoadSeats(const std::map<std::string, std::string>& env, std::string_view specText, Ledger* ledger, const Json& apiConfig, std::optional<double> now) {
feat(seats): the env file's settings count, and a machine's own seats file ApiConfig and PoolConfig read the seat order, the budgets and the concurrency from the process environment alone, so a `.env` that said REVIEW_GATES_SEAT_ORDER=oauth-first ordered nothing: the Python driver reads those lines from the file under the process environment (config_env, 2026-10-17: a setting lives beside the seats it orders). SeatPool::SettingsOf does the same - the file's placement settings under the process environment, in a map handed to the placement readers alone (ApiConfig, PoolConfig, Seats::ConfigFromEnv) - and leaves the process environment untouched, so no other reader sees a line of the file: a path's or a binary's line reaches nothing, and two drivers reading one .env keep one board and one ledger. The placement settings are one list, SeatPool::PlacementSettings - the fifteen names those readers read - for the map, the run's log and what makes a working directory's .env a seats file; a REVIEW_GATES_* line of any other name stays out of the map and is named, by the run's log and on `seats`'s stderr. One dotenv grammar serves the tokens and the settings (Seats::DotenvLines). `run` and `seats` resolve the file from their own --env-file (SeatPool::ResolveSeatsFile): the --env-file, else $SEATS_ENV_FILE, else the working directory's .env - the operator's, as the Python driver and `seats` read it, not the repository under review's - when it is a file holding a seat or a placement setting, each with a value, or one that cannot be read as UTF-8 text (its error is then the command's) - a readable application .env, its Vite or Supabase lines, one of blank placeholder lines or one that is no file is passed over, and the run says so - else the machine's own $XDG_CONFIG_HOME/crafter-review/env, else ~/.config/crafter-review/env: one file for every repository on a machine, a repository's seats .env winning over it. Each file is read once and the settings and the tokens are taken from that read (SeatsFile::Text), so a file that can be read only once - a pipe, `--env-file <(gpg -d seats.env.gpg)`, a FIFO - gives its settings and its seats alike, and a failed read raises its own error where the tokens are laid over the environment: SeatPool::WithFileTokens, for `run` and `seats` alike, and LoadSeats takes the merged environment and no longer reads a file by path. An empty `seats --env-file` means no file now, as it did for `run`, and the `seats` report names the side that runs first (it said "api seats run first" whatever the order). Runs, Commands and KernelModes, the tests that run `run` and `seats`, point XDG_CONFIG_HOME, and HOST_XDG_CONFIG_HOME for the Flatpak, at a path of their own that nothing creates, so a global file stays out of them; the grammar, the settings and their precedence, the lookup and the one read have their tests, a run with `--env` (a prefix) and with a repeated --env-file, and a `seats` with `--env`, take tokens and settings from the one file, an application's .env and a venv are passed over, a FIFO is read once, no line of the file reaches `stop`, every listed placement setting moves a reader, another REVIEW_GATES_* line is named and a .env of blank lines passed over, and the README, the environment table and the procedure say the new rule.
2026-11-01 01:51:48 +01:00
const std::map<std::string, std::string>& merged = env;
std::vector<std::pair<std::string, Json>> pool;
for (const auto& [n, key] : Seats::ApiKeysFromEnv(merged)) pool.emplace_back(std::format("api{}", n), NewSeat(std::format("api{}", n), "api", key));
for (const auto& [n, token] : Seats::SeatTokensFromEnv(merged)) pool.emplace_back(std::to_string(n), NewSeat(std::to_string(n), "oauth", token));
for (const auto& [n, key] : Seats::OpenRouterKeysFromEnv(merged)) pool.emplace_back(std::format("or{}", n), NewSeat(std::format("or{}", n), "openrouter", key));
pool.emplace_back("local", NewSeat("local", "local", ""));
auto find = [&](std::string_view id) -> const Json* {
for (const auto& [key, seat] : pool) {
if (key == id) return &seat;
}
return nullptr;
};
const std::string spec = Dyn::Strip(specText.empty() ? std::string_view("auto") : specText);
std::vector<std::string> ids;
if (spec == "auto") {
// Never an OpenRouter seat by default.
for (const auto& [key, seat] : pool) {
if (Kind(seat) == "api" || Kind(seat) == "oauth") ids.push_back(key);
}
if (ids.empty()) ids.emplace_back("local");
} else if (spec == "local" || spec == "api" || spec == "oauth" || spec == "openrouter") {
for (const auto& [key, seat] : pool) {
if (Kind(seat) == spec) ids.push_back(key);
}
} else {
std::vector<std::string> bad;
for (const std::string& part : Dyn::Split(spec, ",")) {
const std::string id = Dyn::Strip(part);
if (id.empty()) continue;
ids.push_back(id);
if (!find(id)) bad.push_back(id);
}
if (!bad.empty()) {
std::vector<std::string> configured;
for (const auto& [key, seat] : pool) {
if (key != "local") configured.push_back(key);
}
throw std::invalid_argument(std::format("--seats names seat(s) not configured: {} (configured: {}, local)", Dyn::Join(bad, ", "), Dyn::Join(configured, ", ")));
}
}
std::vector<Json> seats;
for (const std::string& id : ids) seats.push_back(*find(id));
if (std::ranges::any_of(seats, IsOpenRouter) && !std::ranges::all_of(seats, IsOpenRouter)) {
throw std::invalid_argument("--seats: an OpenRouter seat cannot share a pool with Anthropic seats or the local login - a model name means a different model per endpoint");
}
// An API seat's cycle starts on its grant day (2026-10-17): the day
// its organization receives the plan's credits, the Console's
// Promotional-credits expiry minus three days.
const std::map<std::int32_t, std::int32_t> grantDays = Seats::ApiGrantDays(env);
for (Json& s : seats) {
if (Kind(s) != "api") continue;
const auto found = grantDays.find(static_cast<std::int32_t>(Dyn::ParseInt(Dyn::Slice(Id(s), 3)).value_or(0)));
const std::int32_t day = found != grantDays.end() ? found->second : (Dyn::Truthy(apiConfig) ? static_cast<std::int32_t>(Int(AsDouble(Dyn::At(apiConfig, "cycleDay")))) : 1);
s["grantDay"] = day;
if (ledger) ledger->SetDay(Id(s), day);
}
if (ledger) {
const double at = NowOr(now);
for (Json& s : seats) {
if (!IsPaid(s)) continue;
const Json& e = ledger->Entry(Id(s), at);
s["spentUsd"] = AsDouble(Dyn::At(e, "spentUsd"));
const double budget = Dyn::Truthy(apiConfig) ? Budget(s, apiConfig) : std::numeric_limits<double>::infinity();
const auto [out, why] = LedgerVerdict(e, budget);
if (out) {
s["state"] = "out";
s["error"] = why;
// Why the ledger holds the seat out: a credit-out is
// probed again, a spent budget is not.
s["ledgerOut"] = LedgerOut(e, budget);
}
}
}
return seats;
}
void ApplyProbe(Json& s, const Json& r) {
s["state"] = Dyn::Has(r, "state") ? r.at("state") : Json("unknown");
s["windows"] = Dyn::Or(Dyn::Get(r, "windows"), Dyn::EmptyDict());
s["util"] = Dyn::Get(r, "util");
s["binding"] = Dyn::Truthy(Dyn::Get(r, "binding")) ? r.at("binding") : Json("");
s["resetsAt"] = Dyn::Get(r, "resetsAt");
s["error"] = Dyn::Get(r, "error");
s["probedWith"] = Dyn::Get(r, "probedWith");
if (Kind(s) == "oauth" && Dyn::Truthy(s.at("windows"))) Reutil(s);
if (s.at("state") == "limited" && Dyn::Truthy(s.at("resetsAt"))) s["pauseUntil"] = AsDouble(s.at("resetsAt")) + 30;
}
std::string Describe(const Json& s) {
if (IsPaid(s)) {
std::string state = Dyn::Str(s.at("state"));
if (Dyn::Truthy(Dyn::Get(s, "error")) && s.at("state") != "ok") state += std::format(" ({})", Dyn::Str(s.at("error")));
const Json& remaining = Dyn::Get(s, "remainingUsd");
return std::format("{}, ${:.2f} spent this cycle{}", state, AsDouble(s.at("spentUsd")), remaining.is_null() ? std::string() : std::format(", ${:.2f} left on the key", AsDouble(remaining)));
}
if (!Dyn::Truthy(s.at("windows"))) {
return Dyn::Str(s.at("state")) + (Dyn::Truthy(Dyn::Get(s, "error")) ? std::format(" ({})", Dyn::Str(s.at("error"))) : std::string());
}
std::vector<std::string> parts;
const Json& windows = s.at("windows");
for (auto it = windows.begin(); it != windows.end(); ++it) {
const Json& u = Dyn::Get(it.value(), "utilization");
if (u.is_null()) continue;
std::string label = it.key();
if (it.key() == "five_hour") {
label = "5h";
} else if (it.key() == "seven_day") {
label = "7d";
} else if (it.key() == Seats::FableWindow || Seats::IsFableWindow(it.key())) {
label = "fable";
}
parts.push_back(std::format("{} {}%", label, Seats::Fmt(u)));
}
const Json& binding = s.at("binding");
return std::format("{}: {}{}", Dyn::Str(s.at("state")), Dyn::Join(parts, ", "), Dyn::Truthy(binding) ? std::format(" (binding {})", Dyn::Str(binding)) : std::string());
}
namespace {
// A paid key after its probe (started at probedAt): the seat takes
// the spend the file holds, every driver's; a lift puts a held-out
// seat back (an API key's with its cycle restarted), a credit error
// recorded since the probe started stands over its answer, the credit
// error takes it out (an API key's with the ledger's record and the
// next credits' date in the reason), an unreadable probe leaves a
// held-out seat held out.
void AfterPaidProbe(Json& s, const Json& held, Ledger* ledger, double probedAt, double now, const Log& log) {
const std::string event = ledger ? ledger->Reconcile(Id(s), s.at("state"), probedAt, now) : std::string();
const Json entry = ledger ? ledger->Entry(Id(s), now) : Json::object();
if (ledger) s["spentUsd"] = Num(Dyn::Get(entry, "spentUsd"));
if (event == "new-grant") {
s["ledgerOut"] = nullptr;
s["error"] = nullptr;
log(std::format("seat {}: the key answers again - new credits; the ledger cycle restarts (from {})", Id(s), Dyn::Str(entry.at("cycle"))));
} else if (event == "credit-back") {
s["ledgerOut"] = nullptr;
s["error"] = nullptr;
log(std::format("seat {}: the key's limit has room again - back, its cycle's spend kept", Id(s)));
} else if (s.at("state") == "ok" && Dyn::Truthy(Dyn::Get(entry, "outSince"))) {
s["state"] = "out";
s["ledgerOut"] = "credit";
s["error"] = LedgerVerdict(entry, std::numeric_limits<double>::infinity()).second;
} else if (s.at("state") == "ok") {
s["ledgerOut"] = nullptr;
} else if (s.at("state") == "out" && IsOpenRouter(s)) {
// The probe's error says why.
s["ledgerOut"] = "credit";
} else if (s.at("state") == "out") {
const Json e = ledger ? ledger->Entry(Id(s), now) : Json::object();
const Json& outSince = Dyn::Get(e, "outSince");
const Json& day = Dyn::Get(s, "grantDay");
s["ledgerOut"] = "credit";
s["error"] = std::format("credit balance too low{}; next credits due {}", Dyn::Truthy(outSince) ? std::format(" since {}", Dyn::Slice(Dyn::Str(outSince), 0, 16)) : std::string(),
NextGrant(now, Dyn::Truthy(day) ? static_cast<std::int32_t>(Int(AsDouble(day))) : 1, outSince));
} else if (s.at("state") == "unknown" && Dyn::Truthy(held)) {
s["state"] = "out";
s["error"] = std::format("{} (probe unreadable: {})", Dyn::Str(held), Dyn::Str(Dyn::Get(s, "error")));
}
// A key that answers with its cycle's budget spent is out on that
// budget, as LoadSeats holds such a seat out: an OpenRouter key's
// lift keeps its spend, and another driver may have spent on a
// grant it saw. Left ok, the seat would be neither placed nor out.
if (ledger && s.at("state") == "ok") {
if (const auto [out, why] = LedgerVerdict(entry, Budget(s, ledger->ApiConfig())); out) {
s["state"] = "out";
s["ledgerOut"] = "budget";
s["error"] = why;
}
}
}
}
void Probe(std::vector<Json>& seats, const std::string& claudeBin, double timeout, const std::vector<std::string>& models, const Log& log, const ProbeFunction& probe, Ledger* ledger,
std::optional<double> now, const OpenRouterProbeFunction& probeKey) {
const ProbeFunction run = probe ? probe : ProbeFunction([](std::int32_t seat, const std::string& token, const std::string& model, double t, const std::string& bin, bool local, bool api) {
return Seats::ProbeSeat(seat, token, {.model = model, .timeoutS = t, .claudeBin = bin, .local = local, .api = api});
});
// OAuth seats probe on the lens model when that is Fable (its window
// is seen only there), else on Haiku; API seats and the local login
// on Haiku.
std::optional<std::string> fable;
for (const std::string& m : models) {
if (Dyn::Lower(m).contains("fable")) {
fable = m;
break;
}
}
if (!models.empty() && std::ranges::any_of(seats, IsOpenRouter)) {
const Json prices = FetchOpenRouterPrices(models, log);
for (auto it = prices.begin(); it != prices.end(); ++it) {
const Json& p = it.value();
if (!Dyn::Truthy(p)) {
log(std::format("openrouter: NO PRICE for {} - its spend is not counted", it.key()));
continue;
}
log(std::format("openrouter: {} priced ${:.3f}/${:.3f} per M in/out, cache read {}", it.key(), AsDouble(p.at("prompt")) * 1e6, AsDouble(p.at("completion")) * 1e6,
Dyn::Get(p, "cacheRead").is_null() ? std::string("n/a") : std::format("${:.4f}", AsDouble(p.at("cacheRead")) * 1e6)));
}
}
const std::string lensModel = fable.value_or(std::string(Seats::ProbeModel));
for (Json& s : seats) {
const std::string kind = Kind(s);
if (s.at("state") == "out" && !(IsPaid(s) && Dyn::Get(s, "ledgerOut") == "credit")) continue;
const std::string id = Id(s);
const std::string secret = Dyn::StrOr(Dyn::Get(s, "secret"), "");
if (kind == "openrouter") {
// The ledger's verdict before the probe.
const Json held = s.at("state") == "out" ? Dyn::Get(s, "error") : Json();
const double probedAt = NowOr(now);
const Json r = probeKey ? probeKey(secret, timeout) : ProbeOpenRouter(secret, timeout);
ApplyProbe(s, r);
s["remainingUsd"] = Dyn::Get(r, "remaining");
AfterPaidProbe(s, held, ledger, probedAt, NowOr(now), log);
log(std::format("seat {} ({}): {}", id, kind, Describe(s)));
continue;
}
const std::int32_t n = kind == "api" ? static_cast<std::int32_t>(Dyn::ParseInt(Dyn::Slice(id, 3)).value_or(0)) : (kind == "local" ? 0 : static_cast<std::int32_t>(Dyn::ParseInt(id).value_or(0)));
Json r;
if (kind == "api") {
// The ledger's verdict before the probe.
const Json held = s.at("state") == "out" ? Dyn::Get(s, "error") : Json();
const double probedAt = NowOr(now);
ApplyProbe(s, run(n, secret, std::string(Seats::ProbeModel), timeout, claudeBin, false, true));
AfterPaidProbe(s, held, ledger, probedAt, NowOr(now), log);
log(std::format("seat {} ({}): {}", id, kind, Describe(s)));
continue;
}
if (kind == "local") {
r = run(n, "", lensModel, timeout, claudeBin, true, false);
} else {
r = run(n, secret, lensModel, timeout, claudeBin, false, false);
if (fable && Seats::NeedsHaiku(r)) r = Seats::MergeFableProbe(run(n, secret, std::string(Seats::ProbeModel), timeout, claudeBin, false, false), r);
}
ApplyProbe(s, r);
log(std::format("seat {} ({}): {}", id, kind, Describe(s)));
}
}
// ---- placement -------------------------------------------------------------------------
namespace {
// A finite number >= 0 in Dyn::ParseFloat's syntax, else the default.
double EnvNumber(const std::map<std::string, std::string>& env, const std::string& name, double fallback) {
const std::string text = EnvOf(env, name);
const std::optional<double> n = Dyn::ParseFloat(text);
return n && !Dyn::Strip(text).empty() && std::isfinite(*n) && *n >= 0 ? *n : fallback;
}
// A pass's share of the weekly window: the config's, else the max
// figure.
double Cost7d(const Json& config) {
return Dyn::Has(config, "lensCost7dPct") ? AsDouble(config.at("lensCost7dPct")) : LensCost7dPct({});
}
// The weekly window with the passes in flight and this one projected
// on it stays under the cutoff: the room of an account whose burst
// window is the weekly one.
bool WeeklyRoom(const Json& s, const Json& config) {
const double ahead = static_cast<double>(Running(s) + 1) * Cost7d(config) + Num(Dyn::Get(Foreign(s), "cost7d"));
const Json& windows = Dyn::At(s, "windows");
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (it.key() == "five_hour" || Seats::IsFableWindow(it.key())) continue;
if (Num(Dyn::Get(it.value(), "utilization")) + ahead >= Seats::LongWindowCutoff) return false;
}
return true;
}
}
Json PoolConfig(const std::map<std::string, std::string>& env, std::string_view effort) {
Json config = Seats::ConfigFromEnv(env);
if (Dyn::Strip(EnvOf(env, "REVIEW_GATES_LENS_COST_PCT")).empty()) config["lensCostPct"] = LensCostPct(effort);
config["lensCost7dPct"] = EnvNumber(env, "REVIEW_GATES_LENS_COST_7D_PCT", LensCost7dPct(effort));
config["oauthConcurrency"] = static_cast<std::int64_t>(EnvNumber(env, "REVIEW_GATES_OAUTH_CONCURRENCY", static_cast<double>(OauthConcurrency)));
return config;
}
double OauthProjected(const Json& s, const Json& config) {
// Where the seat's burst window will be with its running processes -
// this driver's at its projection, the other drivers' at theirs (the
// board): the 5-hour window, or the weekly one on an account without
// a 5-hour limit. Unknown = at the ceiling, ranked last.
if (Seats::BurstWindow(Dyn::At(s, "windows")) == "seven_day") {
return AsDouble(s.at("windows").at("seven_day").at("utilization")) + static_cast<double>(Running(s)) * Cost7d(config) + Num(Dyn::Get(Foreign(s), "cost7d"));
}
const Json& five = Dyn::Get(FiveHour(s), "utilization");
const double base = five.is_null() ? AsDouble(config.at("maxUtil")) : AsDouble(five);
return base + static_cast<double>(Running(s)) * AsDouble(config.at("lensCostPct")) + AsDouble(Foreign(s).at("cost5h"));
}
std::vector<std::string> ExpireWindows(Json& s, double now) {
// 2026-10-13: S153 sat in a 30-second pause loop for 30 minutes after
// the 20:20 resets - nothing was running, so no rate event ever
// replaced the 83-89 % readings.
std::vector<std::string> done;
if (!Dyn::Truthy(Dyn::Get(s, "windows"))) return done;
for (auto it = s["windows"].begin(); it != s["windows"].end(); ++it) {
Json& w = it.value();
if (Dyn::Truthy(Dyn::Get(w, "resetsAt")) && AsDouble(w.at("resetsAt")) + 30 <= now && Num(Dyn::Get(w, "utilization")) > 0) {
w["utilization"] = 0;
w["resetsAt"] = 0;
done.push_back(it.key());
}
}
return done;
}
bool Eligible(Json& s, double now, const Json& config, const Json& apiConfig) {
if (s.at("state") == "out" || s.at("state") == "invalid" || now < AsDouble(s.at("pauseUntil"))) return false;
if (IsPaid(s)) {
// The cap counts every driver's processes.
return Running(s) + Int(AsDouble(Foreign(s).at("count"))) < Concurrency(s, apiConfig) && ApiRoom(s, apiConfig);
}
ExpireWindows(s, now);
// Its pause is over (checked above): back in placement, its windows
// decide.
if (s.at("state") == "limited") s["state"] = "ok";
// So many passes per account at once, every driver's counted,
// whatever its windows (2026-10-31).
const std::int64_t cap = Dyn::Has(config, "oauthConcurrency") ? Int(AsDouble(config.at("oauthConcurrency"))) : OauthConcurrency;
if (Running(s) + Int(AsDouble(Foreign(s).at("count"))) >= cap) return false;
const std::optional<std::string> burst = Seats::BurstWindow(s.at("windows"));
// No reading (no probe, or it failed): usable, ranked at the ceiling -
// after every known seat.
if (!burst) return true;
// No 5-hour limit on the account: the weekly cutoff alone, with the
// passes projected on it.
if (*burst == "seven_day") return WeeklyRoom(s, config);
return OauthProjected(s, config) + AsDouble(config.at("lensCostPct")) <= AsDouble(config.at("maxUtil")) && LongOk(s);
}
std::vector<Json*> Ranked(std::vector<Json>& seats, double now, const Json& config, const Json& apiConfig) {
std::vector<Json*> api;
std::vector<Json*> oauth;
for (Json& s : seats) {
if (!Eligible(s, now, config, apiConfig)) continue;
(IsPaid(s) ? api : oauth).push_back(&s);
}
auto spend = [&](const Json* s) {
return std::pair(AsDouble(s->at("spentUsd")) + static_cast<double>(Running(*s)) * LensCost(*s, apiConfig) + AsDouble(Foreign(*s).at("costUsd")), Id(*s));
};
std::ranges::stable_sort(api, [&](const Json* a, const Json* b) { return spend(a) < spend(b); });
if (config.at("policy") == "pack") {
std::ranges::stable_sort(oauth, [](const Json* a, const Json* b) { return std::pair(Kind(*a) != "oauth", Id(*a)) < std::pair(Kind(*b) != "oauth", Id(*b)); });
} else {
auto projected = [&](const Json* s) { return std::pair(Seats::Quantize(OauthProjected(*s, config)), Id(*s)); };
std::ranges::stable_sort(oauth, [&](const Json* a, const Json* b) { return projected(a) < projected(b); });
}
const bool apiFirst = apiConfig.at("order") == "api-first";
std::vector<Json*> out = apiFirst ? api : oauth;
const std::vector<Json*>& rest = apiFirst ? oauth : api;
out.insert(out.end(), rest.begin(), rest.end());
return out;
}
Json* Pick(std::vector<Json>& seats, double now, const Json& config, const Json& apiConfig) {
const std::vector<Json*> ranked = Ranked(seats, now, config, apiConfig);
return ranked.empty() ? nullptr : ranked.front();
}
// ---- resuming a pass on another seat ------------------------------------------------
fs::path SeatDir(const fs::path& scratch, std::string_view seatId) {
return scratch / std::format("seat-{}", seatId);
}
std::optional<fs::path> CarryTranscript(const fs::path& scratch, std::string_view fromSeat, std::string_view toSeat, std::string_view sessionId, const Log& log) {
// The CLI keeps a session's transcript under
// <CLAUDE_CONFIG_DIR>/projects/<cwd slug>/<session id>.jsonl, and
// `claude --resume <id>` continues it; every process runs in the
// repository, so the slug is the same on the other seat.
if (fromSeat.empty() || toSeat.empty() || sessionId.empty() || fromSeat == toSeat) return std::nullopt;
// glob(<seat>/projects/*/<session>.jsonl), whose * skips dot names.
std::optional<fs::path> source;
std::error_code error;
for (fs::directory_iterator it(SeatDir(scratch, fromSeat) / "projects", error), end; !error && it != end; it.increment(error)) {
if (it->path().filename().string().starts_with('.')) continue;
const fs::path candidate = it->path() / std::format("{}.jsonl", sessionId);
std::error_code missing;
if (fs::exists(fs::symlink_status(candidate, missing))) {
source = candidate;
break;
}
}
if (!source) return std::nullopt;
const fs::path destination = SeatDir(scratch, toSeat) / "projects" / source->parent_path().filename() / source->filename();
Platform::MakeDirs(destination.parent_path(), 0700);
CopyContents(*source, destination);
if (log) {
log(std::format("seat {}: transcript of {} carried over from seat {} ({} bytes) - the pass resumes", toSeat, Dyn::Slice(sessionId, 0, 8), fromSeat, fs::file_size(destination)));
}
return destination;
}
bool AllOut(const std::vector<Json>& seats) {
return std::ranges::all_of(seats, [](const Json& s) { return s.at("state") == "out" || s.at("state") == "invalid"; });
}
std::optional<double> EarliestResume(const std::vector<Json>& seats, double now, std::optional<double> recheck) {
std::vector<double> times;
for (const Json& s : seats) {
if (s.at("state") == "out" || s.at("state") == "invalid") continue;
if (AsDouble(s.at("pauseUntil")) > now) {
times.push_back(AsDouble(s.at("pauseUntil")));
} else if (Kind(s) != "api") {
// The reset of the seat's burst window: the 5-hour one, the
// weekly one on an account without it.
const std::optional<std::string> burst = Seats::BurstWindow(s.at("windows"));
const Json& window = burst ? Dyn::Get(s.at("windows"), *burst) : Dyn::EmptyDict();
if (Dyn::Truthy(Dyn::Get(window, "resetsAt")) && AsDouble(window.at("resetsAt")) + 30 > now) times.push_back(AsDouble(window.at("resetsAt")) + 30);
}
if (recheck && AsDouble(Foreign(s).at("count")) > 0) times.push_back(now + *recheck);
}
if (times.empty()) return std::nullopt;
return std::ranges::min(times);
}
double RetryAfter(std::string_view text, double fallback) {
// re.search(r'retry[- ]after[^0-9]{0,12}(\d+)', text, re.I): the class
// run is greedy, so the digits are the last \d that it leaves.
const std::u32string t = Dyn::Decode(text);
auto letters = [&](std::size_t at, std::string_view word) {
for (std::size_t i = 0; i < word.size(); ++i) {
if (Dyn::IgnoreCaseLetter(t[at + i]) != word[i]) return false;
}
return true;
};
for (std::size_t at = 0; at + 11 <= t.size(); ++at) {
if (!letters(at, "retry") || (t[at + 5] != U'-' && t[at + 5] != U' ') || !letters(at + 6, "after")) continue;
const std::size_t gap = at + 11;
std::size_t run = 0;
while (run < 12 && gap + run < t.size() && !(t[gap + run] >= U'0' && t[gap + run] <= U'9')) ++run;
for (std::size_t k = run + 1; k-- > 0;) {
std::size_t end = gap + k;
std::int64_t seconds = 0;
while (end < t.size() && Dyn::IsDecimal(t[end])) {
seconds = std::min<std::int64_t>(1'000'000, seconds * 10 + Dyn::DecimalValue(t[end]));
++end;
}
if (end > gap + k) return static_cast<double>(std::min<std::int64_t>(900, std::max<std::int64_t>(30, seconds + 5)));
}
}
return fallback;
}
bool Settle(Json& s, Json& record, double now, const Json& config, const Json& apiConfig, Ledger* ledger, const Log& log) {
s["procs"] = Dyn::Add(s.at("procs"), Json(1));
const Json& rate = Dyn::Or(Dyn::Get(record, "rateLimit"), Dyn::EmptyDict());
if (Kind(s) != "api" && Dyn::Truthy(rate)) {
Json windows = Json::object();
for (auto it = rate.begin(); it != rate.end(); ++it) {
const Json& u = Dyn::Get(it.value(), "utilization");
const Json& r = Dyn::Get(it.value(), "resetsAt");
windows[it.key()] = {{"utilization", Dyn::Truthy(u) ? u : Json(0)}, {"resetsAt", Dyn::Truthy(r) ? r : Json(0)}};
}
s["windows"] = std::move(windows);
Reutil(s);
const Json& five = FiveHour(s);
if (Num(Dyn::Get(five, "utilization")) >= AsDouble(config.at("maxUtil")) && Dyn::Truthy(Dyn::Get(five, "resetsAt"))) {
s["pauseUntil"] = std::max(AsDouble(s.at("pauseUntil")), AsDouble(five.at("resetsAt")) + 30);
if (s.at("state") == "ok") log(std::format("seat {}: five_hour window at {}% - no new launches until its reset", Id(s), Seats::Fmt(five.at("utilization"))));
}
}
if (IsPaid(s)) {
if (IsOpenRouter(s)) {
// The harness's own figure is wrong for a model it does not
// know (the CLI) or is the catalogue's guess (OpenCode): the
// seat's spend is tokens x OpenRouter's list prices, and that
// is what the dump sums.
record["costUsdHarness"] = Dyn::Get(record, "costUsd");
record["costUsd"] = OpenRouterCost(Dyn::StrOr(Dyn::Get(record, "model"), ""), Dyn::Or(Dyn::Get(record, "usage"), Dyn::EmptyDict()));
}
const double usd = Num(Dyn::Get(record, "costUsd"));
s["spentUsd"] = AsDouble(s.at("spentUsd")) + usd;
if (ledger) ledger->Add(Id(s), usd, now);
const double budget = Budget(s, apiConfig);
const double spent = AsDouble(s.at("spentUsd"));
if (spent >= budget && s.at("state") != "out" && s.at("state") != "invalid") {
s["state"] = "out";
s["error"] = std::format("budget: ${:.2f} spent this cycle, budget ${}", spent, G(budget));
log(std::format("seat {}: {} - out for the rest of the cycle", Id(s), Dyn::Str(s.at("error"))));
} else if (spent >= 0.8 * budget && spent - usd < 0.8 * budget) {
log(std::format("seat {}: ${:.2f} of ${} spent this cycle", Id(s), spent, G(budget)));
}
}
const Json& status = Dyn::Get(record, "status");
const std::string reason = Dyn::StrOr(Dyn::Get(record, "reason"), "");
if (status == "credits") {
s["state"] = "out";
s["error"] = std::format("credit balance exhausted ({})", Dyn::Slice(reason, 0, 80));
if (ledger && IsPaid(s)) ledger->MarkOut(Id(s), "credit balance too low", now);
log(std::format("seat {}: {} - out, the process is placed again", Id(s), Dyn::Str(s.at("error"))));
return true;
}
if (status == "auth") {
s["state"] = "invalid";
s["error"] = std::format("authentication failure ({})", Dyn::Slice(reason, 0, 80));
log(std::format("seat {}: {} - out, the process is placed again", Id(s), Dyn::Str(s.at("error"))));
return true;
}
if (status == "limit") {
if (IsPaid(s)) {
s["pauseUntil"] = std::max(AsDouble(s.at("pauseUntil")), now + RetryAfter(reason, ApiBackoffS));
log(std::format("seat {}: rate limited - paused {} s, the process is placed again", Id(s), Int(AsDouble(s.at("pauseUntil")) - now)));
return true;
}
const std::string rateType = Dyn::StrOr(Dyn::Get(record, "rateType"), "");
if (rateType == "concurrents") {
// Not a window: the account had too many requests in flight
// at once (a burst of passes released onto one seat when its
// prompt prefix became cached). Seconds, not the window's
// reset.
s["pauseUntil"] = std::max(AsDouble(s.at("pauseUntil")), now + RetryAfter(reason, ConcurrentBackoffS));
s["state"] = "limited";
log(std::format("seat {}: too many requests at once ({}) - paused {} s, the process is placed again", Id(s), Dyn::Slice(reason, 0, 40), Int(AsDouble(s.at("pauseUntil")) - now)));
return true;
}
const Json& binding = s.at("binding");
if (rateType.starts_with("seven_day") || (Dyn::StrOr(binding, "").starts_with("seven_day") && Num(Dyn::Get(s, "util")) >= 100)) {
s["state"] = "out";
s["error"] = std::format("seven-day window rejected ({})", rateType.empty() ? Dyn::Str(binding) : rateType);
log(std::format("seat {}: {} - out for the run, the process is placed again", Id(s), Dyn::Str(s.at("error"))));
return true;
}
const Json& five = FiveHour(s);
s["pauseUntil"] = std::max(AsDouble(s.at("pauseUntil")), Dyn::Truthy(Dyn::Get(five, "resetsAt")) ? AsDouble(five.at("resetsAt")) + 30 : now + OauthFallbackPauseS);
s["state"] = "limited";
log(std::format("seat {}: limit ({}) - paused until {}, the process is placed again", Id(s), Dyn::Slice(reason, 0, 60), Clock(AsDouble(s.at("pauseUntil")))));
return true;
}
if (s.at("state") == "limited" && now >= AsDouble(s.at("pauseUntil"))) s["state"] = "ok";
return false;
}
// ---- the child's credentials -----------------------------------------------------------
fs::path WriteCredentials(const fs::path& configDir, std::string_view token) {
// A setup-token is long-lived and never refreshed by the CLI: the
// expiry only has to be far away.
Platform::MakeDirs(configDir, 0700);
const Json body = {{"claudeAiOauth", {{"accessToken", std::string(token)}, {"expiresAt", Int((Platform::Now() + 365 * 86400) * 1000)}, {"scopes", Json::array({"user:inference"})}}}};
const fs::path path = configDir / ".credentials.json";
// One temp name per writer: the pool's threads launch a fresh seat's
// first passes in the same instant, each sees no credentials file yet
// and each writes one - with a pid-only name one rename found the temp
// file gone (S115, S116, S119 on 2026-10-10). The contents are
// identical, so the last rename to win is fine.
const fs::path temp = std::format("{}.{}.{}.tmp", path.string(), Platform::PublishedPid(), Uuid4().substr(0, 8));
const std::int32_t fd = open(temp.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
if (fd < 0) throw std::runtime_error(std::format("{}: {}", Dyn::ErrnoText(errno), Dyn::Repr(temp.string())));
WriteAll(fd, std::format("{}\n", DumpSpaced(body)));
fs::rename(temp, path);
return path;
}
std::map<std::string, std::string> ChildEnv(const Json& seat, const std::map<std::string, std::string>& base, const fs::path& scratch) {
// `local`: the driver's environment as it is (the machine's own
// login). OAuth: the seat's config dir with its credentials file, no
// token variable. API: the key in ANTHROPIC_API_KEY plus a config dir
// of its own that approves that key, so no login on the machine is
// picked up.
std::map<std::string, std::string> env = base;
// No Anthropic seat ever inherits another endpoint or credential from
// the driver's environment.
for (const std::string_view key : {"ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL"}) env.erase(std::string(key));
const std::string kind = Kind(seat);
if (kind == "local") return env;
const fs::path dir = scratch / std::format("seat-{}", Id(seat));
Platform::MakeDirs(dir, 0700);
env["CLAUDE_CONFIG_DIR"] = dir.string();
const std::string secret = Dyn::StrOr(Dyn::Get(seat, "secret"), "");
if (kind == "oauth") {
if (!fs::exists(dir / ".credentials.json")) WriteCredentials(dir, secret);
} else if (kind == "openrouter") {
// The CLI against OpenRouter's Anthropic-format endpoint
// (/api/v1/messages), the key as a bearer token. Verified
// 2026-10-12 (CLI 2.1.296, xiaomi/mimo-v2.6-pro): tools, thinking,
// cache reads and --json-schema all work; `--effort` does not
// reach the model, and the CLI's total_cost_usd is wrong.
env["ANTHROPIC_BASE_URL"] = std::string(OpenRouterBaseUrl);
env["ANTHROPIC_AUTH_TOKEN"] = secret;
env.erase("CLAUDE_CODE_ENTRYPOINT");
// hasCompletedOnboarding, so --print asks nothing.
Seats::WriteApiKeyApproval(dir, secret);
} else {
env["ANTHROPIC_API_KEY"] = secret;
// The launcher's CLAUDE_CODE_ENTRYPOINT=claude-vscode is what broke
// S111's API seats (2026-10-08, CLI 2.1.294): with it the key is
// refused ("Not logged in") and, once approved, the subscriber
// credits answer "Credit balance is too low". Bisected over every
// CLAUDE_*/ANTHROPIC_* variable: only this one matters.
env.erase("CLAUDE_CODE_ENTRYPOINT");
Seats::WriteApiKeyApproval(dir, secret);
}
return env;
}
// ---- OpenRouter seats ------------------------------------------------------------------
std::string ModelKey(std::string_view model) {
return model.starts_with("openrouter/") ? std::string(model.substr(11)) : std::string(model);
}
Json FetchOpenRouterPrices(const std::vector<std::string>& models, const Log& log, double timeout) {
std::set<std::string> want;
for (const std::string& m : models) {
if (!m.empty()) want.insert(ModelKey(m));
}
Json data = Json::array();
try {
const Json catalogue = Http::JsonBody(Http::UrlOpen(OpenRouterModelsUrl, {}, timeout));
data = Dyn::Or(Dyn::Get(catalogue, "data"), Dyn::EmptyList());
} catch (const std::exception& error) {
if (log) log(std::format("openrouter: price catalogue not fetched ({}) - list prices from the built-in table", Dyn::Slice(error.what(), 0, 80)));
}
const std::lock_guard guard(PricesLock);
for (const Json& m : data) {
const Json& id = Dyn::Get(m, "id");
if (!id.is_string() || !want.contains(id.get<std::string>())) continue;
const Json& p = Dyn::Or(Dyn::Get(m, "pricing"), Dyn::EmptyDict());
const Json& cacheRead = Dyn::Get(p, "input_cache_read");
const Json& cacheWrite = Dyn::Get(p, "input_cache_write");
Prices[id.get<std::string>()] = {
{"prompt", Num(Dyn::Get(p, "prompt"))},
{"completion", Num(Dyn::Get(p, "completion"))},
{"cacheRead", cacheRead.is_null() ? Json() : Json(AsDouble(cacheRead))},
{"cacheWrite", cacheWrite.is_null() ? Json() : Json(AsDouble(cacheWrite))},
};
}
Json out = Json::object();
for (const std::string& key : want) {
if (!Prices.contains(key) && BuiltInPrices().contains(key)) Prices[key] = BuiltInPrices().at(key);
out[key] = Prices.contains(key) ? Prices.at(key) : Json();
}
return out;
}
double OpenRouterCost(std::string_view model, const Json& usage) {
// Uncached input and cache writes at the prompt price (a cache write
// costs nothing extra on OpenRouter unless the catalogue says so),
// cache reads and output at theirs. 0 for a model without a price.
Json p;
{
const std::lock_guard guard(PricesLock);
const auto found = Prices.find(ModelKey(model));
if (found != Prices.end()) p = found->second;
}
if (!Dyn::Truthy(p)) p = Dyn::Get(BuiltInPrices(), ModelKey(model));
if (!Dyn::Truthy(p)) return 0.0;
const double prompt = AsDouble(p.at("prompt"));
const double cacheRead = Dyn::Get(p, "cacheRead").is_null() ? prompt : AsDouble(p.at("cacheRead"));
const double cacheWrite = Dyn::Get(p, "cacheWrite").is_null() ? prompt : AsDouble(p.at("cacheWrite"));
const Json& u = Dyn::Or(usage, Dyn::EmptyDict());
double total = Num(Dyn::Get(u, "input")) * prompt;
total = total + Num(Dyn::Get(u, "cacheCreation")) * cacheWrite;
total = total + Num(Dyn::Get(u, "cacheRead")) * cacheRead;
total = total + Num(Dyn::Get(u, "output")) * AsDouble(p.at("completion"));
return Dyn::Round(total, 6);
}
Json ProbeOpenRouter(const std::string& secret, double timeout) {
Json d;
try {
const Json body = Http::JsonBody(Http::UrlOpen(OpenRouterKeyUrl, {{"Authorization", std::format("Bearer {}", secret)}}, timeout));
d = Dyn::Or(Dyn::Get(body, "data"), Dyn::EmptyDict());
} catch (const Http::HttpError& error) {
return {{"state", error.Code() == 401 || error.Code() == 403 ? "invalid" : "unknown"}, {"error", std::format("HTTP {} from {}", error.Code(), OpenRouterKeyUrl)}};
} catch (const std::exception& error) {
return {{"state", "unknown"}, {"error", std::format("probe failed: {}", Dyn::Slice(error.what(), 0, 100))}};
}
const Json& remaining = Dyn::Get(d, "limit_remaining");
if (!remaining.is_null() && AsDouble(remaining) <= 0) {
return {{"state", "out"}, {"error", std::format("key limit reached (${:.2f} of ${} used)", Num(Dyn::Get(d, "usage")), G(Num(Dyn::Get(d, "limit"))))}, {"remaining", remaining}};
}
return {{"state", "ok"}, {"remaining", remaining}, {"usage", Dyn::Get(d, "usage")}, {"limit", Dyn::Get(d, "limit")}};
}
// ---- the board -------------------------------------------------------------------------
fs::path BoardPath() {
if (const std::optional<std::string> configured = Platform::GetEnv("REVIEW_GATES_BOARD"); configured && !configured->empty()) return *configured;
return fs::path(Platform::Home()) / ".cache" / "review-gates" / BoardFile;
}
std::string SeatKey(std::string_view kind, std::string_view secret) {
if (kind == "local" || secret.empty()) return "local";
return std::format("{}:{}", kind, Sha256Hex(secret).substr(0, 12));
}
std::string MyKey() {
if (const std::optional<Platform::Identity> self = Platform::Self()) return self->Key();
return std::format("{}:0", Platform::PublishedPid());
}
bool DriverAlive(std::string_view key, const fs::path& boardDir, bool reap) {
const std::vector<std::string> parts = Dyn::Split(key, ":", 1);
const std::optional<std::int64_t> pid = parts.size() == 2 ? Dyn::ParseInt(parts[0]) : std::nullopt;
const std::optional<std::int64_t> start = parts.size() == 2 ? Dyn::ParseInt(parts[1]) : std::nullopt;
if (!pid || !start) return false;
const fs::path locks = boardDir / "drivers";
if (const std::optional<bool> held = Platform::ProbeLifetimeLock(locks, *pid, *start)) {
if (!*held && reap) {
// Its driver died; nobody can take that name again.
std::error_code error;
fs::remove(Platform::LifetimeLockFile(locks, *pid, *start), error);
}
return *held;
}
// No lock file: a driver that takes none. In the sandbox a pid is
// another namespace's and says nothing.
if (Platform::InSandbox()) return true;
const std::optional<std::int64_t> running = Platform::ProcStart(*pid);
// No /proc at all: the deadline sweep is the backstop.
if (!running) return !fs::is_directory(Platform::Paths().proc);
return *running == *start;
}
bool ClaimScratch(const fs::path& path) {
// 2026-10-18: two test runs launched seconds apart on one hash shared
// `<hash8>-run-2` and its out/ files - the outputs rule alone saw an
// empty out/ in the first seconds.
fs::create_directories(path);
const fs::path marker = path / ".driver";
const std::string me = MyKey();
std::int32_t fd = open(marker.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
if (fd < 0) {
if (errno != EEXIST) throw std::runtime_error(std::format("{}: {}", Dyn::ErrnoText(errno), Dyn::Repr(marker.string())));
const std::vector<std::string> holder = Dyn::Split(Dyn::Strip(Platform::ReadFile(marker).value_or("")), ":", 1);
const std::optional<std::int64_t> pid = holder.size() == 2 ? Dyn::ParseInt(holder[0]) : std::nullopt;
const std::optional<std::int64_t> start = holder.size() == 2 ? Dyn::ParseInt(holder[1]) : std::nullopt;
if (pid && start && *pid != 0 && *pid != Platform::PublishedPid() && DriverAlive(std::format("{}:{}", *pid, *start), BoardPath().parent_path())) return false;
fd = open(marker.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (fd < 0) throw std::runtime_error(std::format("{}: {}", Dyn::ErrnoText(errno), Dyn::Repr(marker.string())));
}
WriteAll(fd, me);
return true;
}
std::vector<std::string> MergeWindows(Json& into, const Json& other, double now) {
// A window whose reset has passed is ignored on either side; of the
// rest the newer resetsAt wins, and within one reset epoch the higher
// utilization: utilization never falls inside an epoch (2026-10-18:
// 1948 rate_limit_events of three runs, 942 rises, 0 falls), so the
// merge is exact without a timestamp.
std::vector<std::string> changed;
const Json& readings = Dyn::Or(other, Dyn::EmptyDict());
for (auto it = readings.begin(); it != readings.end(); ++it) {
const Json& w = it.value();
if (!w.is_object() || Dyn::Get(w, "utilization").is_null()) continue;
const double r = Num(Dyn::Get(w, "resetsAt"));
if (r + 30 <= now) continue;
const Json& found = Dyn::Get(into, it.key());
const Json* current = found.is_object() ? &found : nullptr;
const double cr = current ? Num(Dyn::Get(*current, "resetsAt")) : 0.0;
if (!current || Dyn::Get(*current, "utilization").is_null() || cr + 30 <= now || r > cr || (r == cr && AsDouble(w.at("utilization")) > AsDouble(current->at("utilization")))) {
into[it.key()] = {{"utilization", w.at("utilization")}, {"resetsAt", Dyn::Get(w, "resetsAt")}};
changed.push_back(it.key());
}
}
return changed;
}
Board::Board(fs::path path, std::string me, std::optional<std::string> boot, Log log)
: path(std::move(path)), me(me.empty() ? MyKey() : std::move(me)), boot(boot ? *boot : Platform::BootId()), log(log ? std::move(log) : Log([](const std::string&) {})) {
data = {{"version", 1}, {"drivers", Json::object()}, {"seats", Json::object()}};
}
void Board::Warn(const std::string& message) {
if (warned) return;
warned = true;
log(std::format("board: {} - placement continues on this driver's own view", message));
}
Json Board::Update(const std::function<bool(Json&)>& change) {
data = Platform::LockedUpdate(path, [&](Json& d) {
if (!Dyn::Has(d, "version")) d["version"] = 1;
if (!Dyn::Has(d, "drivers")) d["drivers"] = Json::object();
if (!Dyn::Has(d, "seats")) d["seats"] = Json::object();
return change(d);
}, data, 0600, [this](const std::string& message) { Warn(message); });
return data;
}
bool Board::Alive(std::string_view key, const Json& drivers, bool reap) const {
if (key == me) return true;
const std::vector<std::string> parts = Dyn::Split(key, ":", 1);
if (parts.size() != 2 || !Dyn::ParseInt(parts[0]) || !Dyn::ParseInt(parts[1])) return false;
const Json& d = Dyn::Or(Dyn::Get(drivers, key), Dyn::EmptyDict());
if (!boot.empty() && Dyn::Truthy(Dyn::Get(d, "boot")) && d.at("boot") != boot) return false;
return DriverAlive(key, path.parent_path(), reap);
}
std::pair<bool, std::vector<std::string>> Board::Sweep(Json& d, double now, bool reap) const {
std::vector<std::string> lines;
bool changed = false;
Json& drivers = d["drivers"];
std::vector<std::string> dead;
for (auto it = drivers.begin(); it != drivers.end(); ++it) {
if (!Alive(it.key(), drivers, reap)) dead.push_back(it.key());
}
for (const std::string& key : dead) {
drivers.erase(key);
changed = true;
}
std::vector<std::string> gone;
Json& seats = d["seats"];
for (auto it = seats.begin(); it != seats.end(); ++it) {
Json& s = it.value();
if (!Dyn::Get(s, "inflight").is_object()) s["inflight"] = Json::object();
Json& inflight = s["inflight"];
// {owner: entries}, in the order they were met.
std::vector<std::pair<std::string, std::int64_t>> deadOwners;
std::vector<std::pair<std::string, std::int64_t>> lateOwners;
auto count = [](std::vector<std::pair<std::string, std::int64_t>>& list, const std::string& owner) {
for (auto& [o, n] : list) {
if (o == owner) {
++n;
return;
}
}
list.emplace_back(owner, 1);
};
std::vector<std::string> drop;
for (auto e = inflight.begin(); e != inflight.end(); ++e) {
const std::string owner = Owner(e.key());
if (owner != me && !drivers.contains(owner)) {
count(deadOwners, owner);
drop.push_back(e.key());
} else if (Num(Dyn::Get(e.value(), "deadline")) <= now) {
count(lateOwners, owner);
drop.push_back(e.key());
}
}
for (const std::string& key : drop) inflight.erase(key);
const std::string name = Dyn::StrOr(Dyn::Get(s, "id"), it.key());
for (const auto& [owner, n] : deadOwners) lines.push_back(std::format("swept pid {} (dead) with {} in-flight entr{} on seat {}", PidOf(owner), n, n == 1 ? "y" : "ies", name));
for (const auto& [owner, n] : lateOwners) lines.push_back(std::format("swept {} in-flight entr{} of pid {} past their deadline on seat {}", n, n == 1 ? "y" : "ies", PidOf(owner), name));
const Json& state = Dyn::Get(s, "state");
if (Dyn::Truthy(state)) {
const Json& until = Dyn::Get(state, "until");
const Json& by = Dyn::Get(state, "by");
if (!until.is_null() ? AsDouble(until) <= now : (by != me && !(by.is_string() && drivers.contains(by.get<std::string>())))) {
s["state"] = nullptr;
changed = true;
}
}
bool liveWindow = false;
for (const Json& w : Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict())) liveWindow = liveWindow || (w.is_object() && Num(Dyn::Get(w, "resetsAt")) + 30 > now);
if (inflight.empty() && !Dyn::Truthy(Dyn::Get(s, "state")) && !liveWindow && Num(Dyn::Get(s, "pauseUntil")) <= now) gone.push_back(it.key());
}
for (const std::string& key : gone) {
seats.erase(key);
changed = true;
}
return {changed || !lines.empty(), lines};
}
Json& Board::Record(Json& d, const Json& seat) const {
const std::string key = KeyOf(seat);
Json& seats = d["seats"];
if (!seats.contains(key) || seats[key].is_null()) {
seats[key] = {{"id", seat.at("id")}, {"kind", seat.at("kind")}, {"windows", Json::object()}, {"pauseUntil", 0.0}, {"state", nullptr}, {"inflight", Json::object()}};
}
return seats[key];
}
bool Board::StateHolds(const Json& state, const Json& d, double now) {
const Json& until = Dyn::Get(state, "until");
if (!until.is_null()) return AsDouble(until) > now;
const Json& by = Dyn::Get(state, "by");
return by.is_string() && d.at("drivers").contains(by.get<std::string>());
}
Others Board::OthersOf(const Json& d) const {
// Memory and stacks are the machine's, not a seat's: every other
// driver's pass counts, whatever account it runs on.
Others o;
for (const Json& b : d.at("seats")) {
const Json& inflight = Dyn::Or(Dyn::Get(b, "inflight"), Dyn::EmptyDict());
for (auto it = inflight.begin(); it != inflight.end(); ++it) {
if (Owner(it.key()) == me) continue;
++o.count;
if (Dyn::Get(it.value(), "mb").is_null()) ++o.unmeasured;
o.mb += Num(Dyn::Get(it.value(), "mb"));
if (Dyn::Truthy(Dyn::Get(it.value(), "stack"))) o.stacks.insert(Dyn::Str(it.value().at("stack")));
}
}
o.mb = Dyn::Round(o.mb, 1);
return o;
}
std::pair<bool, std::vector<std::string>> Board::FoldInto(std::vector<Json>& seats, double now, Json& d) {
auto [changed, lines] = Sweep(d, now);
others = OthersOf(d);
for (Json& s : seats) {
Json f = ZeroForeign();
f["drivers"] = Json::object();
const Json& b = Dyn::Get(d.at("seats"), KeyOf(s));
if (Dyn::Truthy(b)) {
const Json& inflight = Dyn::Or(Dyn::Get(b, "inflight"), Dyn::EmptyDict());
std::int64_t count = 0;
double cost5h = 0.0;
double cost7d = 0.0;
double costUsd = 0.0;
for (auto it = inflight.begin(); it != inflight.end(); ++it) {
const std::string owner = Owner(it.key());
if (owner == me) continue;
++count;
cost5h += Num(Dyn::Get(it.value(), "cost5h"));
cost7d += Num(Dyn::Get(it.value(), "cost7d"));
costUsd += Num(Dyn::Get(it.value(), "costUsd"));
f["drivers"][owner] = Dyn::Has(f["drivers"], owner) ? f["drivers"][owner].get<std::int64_t>() + 1 : 1;
}
f["count"] = count;
f["cost5h"] = cost5h;
f["cost7d"] = cost7d;
f["costUsd"] = costUsd;
if (!IsPaid(s) && !MergeWindows(s["windows"], Dyn::Get(b, "windows"), now).empty()) Reutil(s);
const double pause = Num(Dyn::Get(b, "pauseUntil"));
if (pause > Num(Dyn::Get(s, "pauseUntil"))) {
s["pauseUntil"] = pause;
if (pause > now) lines.push_back(std::format("seat {}: paused until {} - another driver's limit", Id(s), Clock(pause)));
}
const Json& st = Dyn::Get(b, "state");
if (Dyn::Truthy(st) && std::ranges::contains(StateShared, Kind(s)) && s.at("state") != "out" && s.at("state") != "invalid" && StateHolds(st, d, now)) {
s["state"] = Dyn::At(st, "state");
s["stateFrom"] = Dyn::Get(st, "by");
const Json& error = Dyn::Get(st, "error");
s["error"] = std::format("{} (seen by pid {})", Dyn::Truthy(error) ? Dyn::Str(error) : Dyn::Str(st.at("state")), PidOf(Dyn::StrOr(Dyn::Get(st, "by"), "?")));
lines.push_back(std::format("seat {}: {} - {}", Id(s), Dyn::Str(s.at("state")), Dyn::Str(s.at("error"))));
}
}
f["cost5h"] = Dyn::Round(AsDouble(f.at("cost5h")), 4);
f["cost7d"] = Dyn::Round(AsDouble(f.at("cost7d")), 4);
f["costUsd"] = Dyn::Round(AsDouble(f.at("costUsd")), 4);
s["foreign"] = std::move(f);
}
return {changed, lines};
}
bool Board::PublishSeat(Json& d, const Json& s, double now, bool probed) const {
// Readings merged, pause raised, an out/invalid verdict of our own
// written (OAuth and local seats; a paid seat's credit-out is the
// ledger's, its budget-out this driver's alone); a seat our own probe
// read ok clears a verdict another driver left.
Json& b = Record(d, s);
bool changed = !Dyn::Equal(Dyn::Get(b, "id"), s.at("id"));
b["id"] = s.at("id");
if (!IsPaid(s)) {
if (!Dyn::Has(b, "windows")) b["windows"] = Json::object();
if (!MergeWindows(b["windows"], Dyn::Get(s, "windows"), now).empty()) changed = true;
}
if (Num(Dyn::Get(s, "pauseUntil")) > Num(Dyn::Get(b, "pauseUntil"))) {
b["pauseUntil"] = AsDouble(s.at("pauseUntil"));
changed = true;
}
if (std::ranges::contains(StateShared, Kind(s))) {
const Json& state = s.at("state");
const Json& held = Dyn::Get(b, "state");
if ((state == "out" || state == "invalid") && !Dyn::Truthy(Dyn::Get(s, "stateFrom")) && (!Dyn::Truthy(held) || Dyn::Get(held, "by") == me)) {
Json until;
if (state == "out") {
// A seven-day rejection stands until that window resets.
const Json& seven = Dyn::Or(Dyn::Get(Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict()), "seven_day"), Dyn::EmptyDict());
if (Dyn::Truthy(Dyn::Get(seven, "resetsAt"))) until = AsDouble(seven.at("resetsAt")) + 30;
}
const Json record = {{"state", state}, {"error", Dyn::Slice(Dyn::StrOr(Dyn::Get(s, "error"), ""), 0, 160)}, {"until", until}, {"by", me}};
if (!Dyn::Equal(held, record)) {
b["state"] = record;
changed = true;
}
} else if (probed && state == "ok" && Dyn::Truthy(held)) {
b["state"] = nullptr;
changed = true;
}
}
return changed;
}
void Board::Join(const Json& info, double now) {
Update([&](Json& d) {
Sweep(d, now);
Json entry = info;
entry["since"] = now;
entry["boot"] = boot;
d["drivers"][me] = std::move(entry);
return true;
});
}
void Board::Leave() {
try {
Update([&](Json& d) {
bool changed = d["drivers"].erase(me) > 0;
for (auto it = d["seats"].begin(); it != d["seats"].end(); ++it) {
if (!Dyn::Get(it.value(), "inflight").is_object()) continue;
Json& inflight = it.value()["inflight"];
std::vector<std::string> mine;
for (auto e = inflight.begin(); e != inflight.end(); ++e) {
if (e.key().starts_with(std::format("{}/", me))) mine.push_back(e.key());
}
for (const std::string& key : mine) {
inflight.erase(key);
changed = true;
}
}
return changed;
});
} catch (const std::exception& error) {
Warn(std::format("leave failed ({})", error.what()));
}
}
void Board::Publish(const std::vector<Json>& seats, double now, bool probed) {
Update([&](Json& d) {
bool changed = Sweep(d, now).first;
for (const Json& s : seats) changed = PublishSeat(d, s, now, probed) || changed;
return changed;
});
}
std::vector<std::string> Board::Fold(std::vector<Json>& seats, double now) {
std::vector<std::string> lines;
Update([&](Json& d) {
auto [changed, folded] = FoldInto(seats, now, d);
lines = std::move(folded);
return changed;
});
for (const std::string& line : lines) log(std::format("board: {}", line));
return lines;
}
std::int64_t Board::TotalInflight(const Json& d) {
std::int64_t total = 0;
for (const Json& s : Dyn::Or(Dyn::Get(d, "seats"), Dyn::EmptyDict())) total += static_cast<std::int64_t>(Dyn::Or(Dyn::Get(s, "inflight"), Dyn::EmptyDict()).size());
return total;
}
std::pair<double, std::string> Board::MemoryBase(std::int64_t free, double now) {
// MemAvailable as it was with no pass in flight on the machine. With
// other drivers' passes in flight the recorded reading stands: those
// passes are in the budget at what they were admitted at, and the
// reading of now has their use taken off already - it would count
// them twice. With no record, or a pass in flight that records no
// memory, `free` - the safe side.
std::optional<std::pair<double, std::string>> result;
Update([&](Json& d) {
const bool changed = Sweep(d, now).first;
std::vector<const Json*> entries;
for (const Json& s : d.at("seats")) {
for (const Json& e : Dyn::Or(Dyn::Get(s, "inflight"), Dyn::EmptyDict())) entries.push_back(&e);
}
const Json memory = Dyn::Get(d, "memory").is_object() ? d.at("memory") : Json::object();
if (entries.empty()) {
d["memory"] = {{"freeMb", free}, {"at", now}, {"by", me}};
result = {static_cast<double>(free), "no pass in flight on this machine"};
return true;
}
std::vector<double> held;
for (const Json* e : entries) held.push_back(Num(Dyn::Get(*e, "mb")));
const double mb = Dyn::Sum(held);
const std::size_t n = entries.size();
const std::string plural = n != 1 ? "es" : "";
if (!Dyn::Get(memory, "freeMb").is_null() && std::ranges::all_of(entries, [](const Json* e) { return !Dyn::Get(*e, "mb").is_null(); })) {
result = {AsDouble(memory.at("freeMb")), std::format("the board's reading of {} with no pass in flight; {} pass{} of other drivers in flight hold{} {:.0f} MB of it",
Clock(Num(Dyn::Get(memory, "at"))), n, plural, n == 1 ? "s" : "", mb)};
} else {
const std::string why = Dyn::Get(memory, "freeMb").is_null()
? std::string("no reading on the board")
: std::format("{} of them record no memory", std::ranges::count_if(entries, [](const Json* e) { return Dyn::Get(*e, "mb").is_null(); }));
result = {static_cast<double>(free), std::format("read now with {} pass{} of other drivers in flight ({}): their {:.0f} MB counted again, on the safe side", n, plural, why, mb)};
}
return changed;
});
return result.value_or(std::pair<double, std::string>{static_cast<double>(free), "read now"});
}
Board::Placed Board::Place(std::vector<Json>& seats, double now, const Json& config, const Json& apiConfig, const Chooser& chooser, std::optional<std::int64_t> cap) {
// Two drivers polling in the same instant cannot both take the last
// slot, the last stack or the last of the memory. `cap` is the pool
// width, enforced across every driver on the machine (Jorijn,
// 2026-10-18).
Placed placed;
std::vector<std::string> lines;
Update([&](Json& d) {
auto [changed, folded] = FoldInto(seats, now, d);
lines = std::move(folded);
placed.ranked = Ranked(seats, now, config, apiConfig);
const std::int64_t total = TotalInflight(d);
if (cap && total >= *cap) {
placed.full = total;
return changed;
}
placed.picked = placed.ranked.empty() ? std::nullopt : chooser(placed.ranked, others);
if (placed.picked) {
const Json& seat = *placed.picked->first;
const Json& job = *placed.picked->second;
const bool paid = IsPaid(seat);
const Json& timeout = Dyn::Get(job, "timeoutMin");
Json entry = {
{"since", now},
{"deadline", now + 60 * (Dyn::Truthy(timeout) ? AsDouble(timeout) : 20.0) * 1.5 + InflightGraceS},
{"cost5h", paid ? Json(0) : config.at("lensCostPct")},
{"cost7d", paid ? Json(0) : Json(Cost7d(config))},
{"costUsd", paid ? LensCostValue(seat, apiConfig) : Json(0)},
};
if (!Dyn::Get(job, "_admitted").is_null()) entry["mb"] = Dyn::Round(AsDouble(job.at("_admitted")), 1);
if (Dyn::Truthy(Dyn::Get(job, "stackKey"))) entry["stack"] = job.at("stackKey");
Record(d, seat)["inflight"][std::format("{}/{}", me, Dyn::Str(Dyn::At(job, "label")))] = std::move(entry);
changed = true;
}
return changed;
});
for (const std::string& line : lines) log(std::format("board: {}", line));
return placed;
}
void Board::Finish(const Json& seat, std::string_view label, double now) {
// Off the board whether or not it is placed again, and the seat's
// settled state onto it.
Update([&](Json& d) {
bool changed = Sweep(d, now).first;
const std::string key = KeyOf(seat);
if (Dyn::Truthy(Dyn::Get(d.at("seats"), key))) {
Json& b = d["seats"][key];
if (Dyn::Get(b, "inflight").is_object() && b["inflight"].erase(std::format("{}/{}", me, label)) > 0) changed = true;
}
return PublishSeat(d, seat, now, false) || changed;
});
}
Json Board::View(double now) const {
Json d;
try {
d = Dyn::LoadJsonFile(path);
} catch (const std::exception&) {
d = Json::object();
}
if (!d.is_object()) d = Json::object();
if (!Dyn::Has(d, "drivers")) d["drivers"] = Json::object();
if (!Dyn::Has(d, "seats")) d["seats"] = Json::object();
// Nothing written, not even a dead driver's lock file taken away.
Sweep(d, now, false);
Json drivers = Json::object();
for (auto it = d.at("drivers").begin(); it != d.at("drivers").end(); ++it) {
Json driver = it.value();
driver["inflight"] = 0;
driver["mb"] = 0.0;
driver["stacks"] = Json::array();
drivers[it.key()] = std::move(driver);
}
Json seats = Json::object();
for (auto it = d.at("seats").begin(); it != d.at("seats").end(); ++it) {
const Json& s = it.value();
Json by = Json::object();
std::int64_t count = 0;
double cost5h = 0.0;
double cost7d = 0.0;
double costUsd = 0.0;
Json oldest;
const Json& inflight = Dyn::Or(Dyn::Get(s, "inflight"), Dyn::EmptyDict());
for (auto e = inflight.begin(); e != inflight.end(); ++e) {
const std::string owner = Owner(e.key());
by[owner] = Dyn::Has(by, owner) ? by[owner].get<std::int64_t>() + 1 : 1;
++count;
cost5h += Num(Dyn::Get(e.value(), "cost5h"));
cost7d += Num(Dyn::Get(e.value(), "cost7d"));
costUsd += Num(Dyn::Get(e.value(), "costUsd"));
const double since = Num(Dyn::Get(e.value(), "since"));
if (oldest.is_null() || since < oldest.get<double>()) oldest = since;
if (drivers.contains(owner)) {
Json& driver = drivers[owner];
driver["inflight"] = driver.at("inflight").get<std::int64_t>() + 1;
driver["mb"] = Dyn::Round(AsDouble(driver.at("mb")) + Num(Dyn::Get(e.value(), "mb")), 1);
if (Dyn::Truthy(Dyn::Get(e.value(), "stack"))) driver["stacks"].push_back(e.value().at("stack"));
}
}
const Json& pause = Dyn::Get(s, "pauseUntil");
seats[it.key()] = {
{"id", Dyn::Get(s, "id")},
{"kind", Dyn::Get(s, "kind")},
{"inflight", count},
{"cost5h", Dyn::Round(cost5h, 4)},
{"cost7d", Dyn::Round(cost7d, 4)},
{"costUsd", Dyn::Round(costUsd, 4)},
{"by", by},
{"oldest", oldest},
{"windows", Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict())},
{"pauseUntil", Dyn::Truthy(pause) ? pause : Json(0)},
{"state", Dyn::Get(s, "state")},
};
}
return {{"path", path.string()}, {"drivers", drivers}, {"seats", seats}, {"memory", Dyn::Get(d, "memory")}};
}
std::vector<std::string> Board::OthersLines(const std::vector<Json>& seats, double, const Json& config, const Json& apiConfig, std::optional<std::int64_t> cap) const {
Json drivers = Json::object();
const Json& all = Dyn::Or(Dyn::Get(data, "drivers"), Dyn::EmptyDict());
for (auto it = all.begin(); it != all.end(); ++it) {
if (it.key() != me) drivers[it.key()] = it.value();
}
const std::int64_t perAccount = Dyn::Has(config, "oauthConcurrency") ? Int(AsDouble(config.at("oauthConcurrency"))) : OauthConcurrency;
const std::string width = cap ? std::format("; {} in flight on this machine, the cap is {}", TotalInflight(data), *cap)
: std::format("; {} in flight on this machine, at most {} per account", TotalInflight(data), perAccount);
if (drivers.empty()) return {std::format("board: {}; no other driver running{}", path.string(), width)};
std::vector<std::string> each;
for (auto it = drivers.begin(); it != drivers.end(); ++it) {
std::int64_t n = 0;
for (const Json& s : seats) {
const Json f = Foreign(s);
const Json& by = Dyn::Or(Dyn::Get(f, "drivers"), Dyn::EmptyDict());
if (Dyn::Has(by, it.key())) n += Int(AsDouble(by.at(it.key())));
}
const Json& d = it.value();
each.push_back(std::format("pid {} ({}, {}) since {}, {} in flight on our seats", PidOf(it.key()), Dyn::Has(d, "run") ? Dyn::Str(d.at("run")) : std::string("?"),
Dyn::Has(d, "effort") ? Dyn::Str(d.at("effort")) : std::string("?"), Platform::LocalTime("%H:%M", Num(Dyn::Get(d, "since"))), n));
}
std::string held;
if (others.count) {
held = std::format("; theirs hold {:.0f} MB of memory", others.mb);
if (!others.stacks.empty()) {
held += std::format(" and {} stack(s) ({})", others.stacks.size(), Dyn::Join(std::vector<std::string>(others.stacks.begin(), others.stacks.end()), ", "));
}
}
std::vector<std::string> lines = {std::format("board: {}; {} other driver(s) running: {}{}{}", path.string(), drivers.size(), Dyn::Join(each, "; "), width, held)};
for (const Json& s : seats) {
const Json f = Foreign(s);
if (!Dyn::Truthy(f.at("count"))) continue;
std::vector<std::string> who;
const Json& by = Dyn::Or(Dyn::Get(f, "drivers"), Dyn::EmptyDict());
for (auto it = by.begin(); it != by.end(); ++it) who.push_back(std::format("{} from pid {}", Dyn::Str(it.value()), PidOf(it.key())));
const std::int64_t count = Int(AsDouble(f.at("count")));
if (IsPaid(s)) {
const std::int64_t c = Concurrency(s, apiConfig);
lines.push_back(std::format(" seat {}: {} in flight ({}) - {} of {} slots free, ${:.2f} reserved", Id(s), count, Dyn::Join(who, ", "), std::max<std::int64_t>(0, c - count - Running(s)), c,
AsDouble(f.at("costUsd"))));
} else if (Seats::BurstWindow(s.at("windows")) == "seven_day") {
lines.push_back(std::format(" seat {}: {} passes in flight ({}) - projected 7d {:.1f} %, no 5-hour limit", Id(s), count, Dyn::Join(who, ", "), OauthProjected(s, config)));
} else {
lines.push_back(std::format(" seat {}: {} passes in flight ({}) - projected 5h {:.1f} %", Id(s), count, Dyn::Join(who, ", "), OauthProjected(s, config)));
}
}
return lines;
}
const Others& Board::OthersNow() const {
return others;
}
const std::string& Board::Me() const {
return me;
}
const fs::path& Board::Path() const {
return path;
}
const Json& Board::Data() const {
return data;
}
// ---- reporting -----------------------------------------------------------------------------
Json Summary(const std::vector<Json>& seats) {
Json out = Json::array();
for (const Json& s : seats) {
Json copy = s;
copy.erase("secret");
out.push_back(std::move(copy));
}
return out;
}
std::string Table(const std::vector<Json>& seats, std::optional<double> nowGiven) {
const double now = NowOr(nowGiven);
std::vector<std::vector<std::string>> rows = {{"seat", "kind", "state", "5h", "7d", "fable", "running", "foreign", "done", "spent", "note"}};
for (const Json& s : seats) {
const Json& windows = s.at("windows");
auto w = [&](std::string_view k) { return Seats::WindowCell(windows, k); };
std::string fable = "—";
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (Seats::IsFableWindow(it.key()) && !Dyn::Get(it.value(), "utilization").is_null()) {
fable = std::format("{}%", Seats::Fmt(it.value().at("utilization")));
break;
}
}
std::string note;
if (s.at("state") != "ok") {
note = Dyn::Slice(Dyn::StrOr(s.at("error"), ""), 0, 60);
} else if (AsDouble(s.at("pauseUntil")) > now) {
note = std::format("paused until {}", Platform::LocalTime("%H:%M", AsDouble(s.at("pauseUntil"))));
}
const Json f = Foreign(s);
rows.push_back({Id(s), Kind(s), Dyn::Str(s.at("state")), w("five_hour"), w("seven_day"), fable, Dyn::Str(s.at("running")),
Dyn::Truthy(f.at("count")) ? Dyn::Str(f.at("count")) : std::string("—"), Dyn::Str(s.at("procs")),
IsPaid(s) ? std::format("${:.2f}", AsDouble(s.at("spentUsd"))) : std::string("—"), note});
}
return Seats::RenderTable(rows);
}
}