diff --git a/AGENTS.md b/AGENTS.md index 561e144..3f1b5f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -106,7 +106,7 @@ Conventions: - An integer beyond 64 bits becomes a float. No file the drivers write holds these in practice, and a file that fails to parse is treated as a damaged one. -- **HTTP.** `FetchHTTP1` ignores `*_proxy` variables. The OpenRouter requests send `User-Agent: crafter-review`; `app_ready` sends `review-gates`. Reason phrases are the standard ones, not the server's. +- **HTTP.** `ClientHTTP1::Fetch` ignores `*_proxy` variables. The OpenRouter requests send `User-Agent: crafter-review`; `app_ready` sends `review-gates`. Reason phrases are the standard ones, not the server's. - **Seat placement** (SeatPool, Seats, Drivers): - An account whose 5-hour limit Anthropic has removed reports the weekly window alone. Its burst window (`Seats::BurstWindow`) is the weekly one: it is ranked and admitted by the weekly window with the passes in flight projected on it (0.04 points per pass at xhigh, 0.125 otherwise), under the 95 % cutoff. - Every OAuth account and the local login takes at most 128 passes at once, every driver's counted. diff --git a/implementations/Crafter.Review-Http.cpp b/implementations/Crafter.Review-Http.cpp index 73247ac..736d226 100644 --- a/implementations/Crafter.Review-Http.cpp +++ b/implementations/Crafter.Review-Http.cpp @@ -1,9 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® -module; -#include - module Crafter.Review:Http_impl; import :Http; import :Json; @@ -20,13 +17,14 @@ namespace Crafter::Review::Http { // ClientTCP's resolve, socket() and connect failures, which happen // while the request goes out, apart from a send's or a receive's. bool WhileSending(const SocketErrorException& failure) { - const std::string_view text = failure.what(); - return failure.kind == SocketErrorKind::Resolver || text.starts_with("Could not connect") || text.starts_with("Could not open socket"); + return failure.phase == SocketPhase::Resolve || failure.phase == SocketPhase::Open || failure.phase == SocketPhase::Connect; } + // "[Errno N] text", with the resolver's own text for a resolver + // code and the C library's for an errno. std::string SocketText(const SocketErrorException& failure) { - if (failure.kind == SocketErrorKind::Resolver) return std::format("[Errno {}] {}", failure.code, gai_strerror(failure.code)); - return Dyn::ErrnoText(failure.code); + if (failure.code().category() == ResolverCategory()) return std::format("[Errno {}] {}", failure.code().value(), failure.code().message()); + return Dyn::ErrnoText(failure.code().value()); } constexpr std::array, 62> Phrases = {{ @@ -61,7 +59,7 @@ namespace Crafter::Review::Http { } Response UrlOpen(std::string_view url, const std::map& headers, double timeout) { - FetchOptionsHTTP1 options; + FetchOptions options; options.headers = {{"accept-encoding", "identity"}, {"connection", "close"}, {"user-agent", "crafter-review"}}; for (const auto& [name, value] : headers) options.headers[Dyn::Lower(name)] = value; // The timeout bounds each blocking step: the connect, the handshake, @@ -70,9 +68,9 @@ namespace Crafter::Review::Http { options.connectTimeout = budget; options.handshakeTimeout = budget; options.timeout = budget; - FetchResultHTTP1 result; + FetchResult result; try { - result = FetchHTTP1(url, options); + result = ClientHTTP1::Fetch(url, options); } catch (const SocketTimeoutException&) { throw Error(Wrapped("timed out")); } catch (const SocketErrorException& failure) { @@ -93,7 +91,7 @@ namespace Crafter::Review::Http { } const std::optional status = Dyn::ParseInt(result.response.status); const auto code = static_cast(status.value_or(0)); - if (result.limited) { + if (result.stop == FetchStop::Loop || result.stop == FetchStop::Limit) { throw HttpError(code, std::format("The HTTP server returned a redirect error that would lead to an infinite loop.\nThe last 30x error message was:\n{}", Reason(code))); } if (code < 200 || code >= 300) throw HttpError(code); diff --git a/implementations/Crafter.Review-PgWire.cpp b/implementations/Crafter.Review-PgWire.cpp index 585c465..5b12760 100644 --- a/implementations/Crafter.Review-PgWire.cpp +++ b/implementations/Crafter.Review-PgWire.cpp @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module; -#include #include #include #include @@ -141,8 +140,10 @@ namespace Crafter::Review::PgWire { std::string ConnectErrorText(const std::exception& error) { if (dynamic_cast(&error)) return "timed out"; if (const auto* failure = dynamic_cast(&error)) { - if (failure->kind == SocketErrorKind::Resolver) return std::format("[Errno {}] {}", failure->code, gai_strerror(failure->code)); - return Dyn::ErrnoText(failure->code); + // "[Errno N] text": the resolver's own text for a resolver + // code, the C library's for an errno. + if (failure->code().category() == ResolverCategory()) return std::format("[Errno {}] {}", failure->code().value(), failure->code().message()); + return Dyn::ErrnoText(failure->code().value()); } return error.what(); } @@ -487,7 +488,7 @@ namespace Crafter::Review::PgWire { } catch (const SocketTimeoutException&) { throw SocketError("timed out", true); } catch (const SocketErrorException& error) { - throw SocketError(Dyn::ErrnoText(error.code), false); + throw SocketError(Dyn::ErrnoText(error.code().value()), false); } } const std::size_t take = std::min(count, buffer.size() - position); @@ -510,7 +511,7 @@ namespace Crafter::Review::PgWire { try { socket->Send(data.data(), static_cast(data.size())); } catch (const SocketErrorException& error) { - throw DbError(std::format("the connection broke ({})", Dyn::ErrnoText(error.code))); + throw DbError(std::format("the connection broke ({})", Dyn::ErrnoText(error.code().value()))); } catch (const SocketClosedException&) { throw DbError(std::format("the connection broke ({})", Dyn::ErrnoText(32))); } @@ -1170,7 +1171,7 @@ namespace Crafter::Review::PgWire { out("-- the driver's SQL broker stopped answering mid-call (timed out); nothing of this call was committed"); return ExitSql; } catch (const SocketErrorException& error) { - out(std::format("-- the driver's SQL broker stopped answering mid-call ({}); nothing of this call was committed", Dyn::ErrnoText(error.code))); + out(std::format("-- the driver's SQL broker stopped answering mid-call ({}); nothing of this call was committed", Dyn::ErrnoText(error.code().value()))); return ExitSql; } catch (const std::exception& error) { out(std::format("-- the driver's SQL broker stopped answering mid-call ({}); nothing of this call was committed", error.what())); diff --git a/interfaces/Crafter.Review-Http.cppm b/interfaces/Crafter.Review-Http.cppm index 7206686..bf472e1 100644 --- a/interfaces/Crafter.Review-Http.cppm +++ b/interfaces/Crafter.Review-Http.cppm @@ -6,7 +6,7 @@ import std; import :Json; // The HTTP the driver needs: app_ready's preflight, the OpenRouter catalogue -// and key probe. On Crafter.Network's FetchHTTP1, which follows redirects; on +// and key probe. On Crafter.Network's ClientHTTP1::Fetch, which follows redirects; on // top of it a status outside 2xx thrown as HttpError, and every other failure // as Error with a fixed text, because those texts reach the run log and // app_ready's detail. Proxies are not consulted (AGENTS.md). diff --git a/project.cpp b/project.cpp index 7dc1890..d5eb735 100644 --- a/project.cpp +++ b/project.cpp @@ -317,7 +317,7 @@ extern "C" Configuration CrafterBuildProject(std::span a Configuration* network = useLocal ? LocalProject({ .projectFile = "../Crafter.Network/project.cpp", .args = depArgs }) : GitProject({ - .source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Network.git", .commit = "f68321685006be65f6bc0436fd0175611f1e0a55" }, + .source = { .url = "https://forgejo.catcrafts.net/Catcrafts/Crafter.Network.git", .commit = "7ee938c98e8c76290eef41b03e49ca5eed27a174" }, .args = depArgs, });