276namespace commercial {
279inline constexpr const char* kLicenseEnvVar =
"SIGNET_COMMERCIAL_LICENSE_KEY";
281inline constexpr const char* kLicenseTierEnvVar =
"SIGNET_COMMERCIAL_LICENSE_TIER";
283inline constexpr const char* kUsageFileEnvVar =
"SIGNET_COMMERCIAL_USAGE_FILE";
285inline constexpr const char* kRuntimeUserEnvVar =
"SIGNET_COMMERCIAL_RUNTIME_USER";
287inline constexpr const char* kRuntimeNodeEnvVar =
"SIGNET_COMMERCIAL_RUNTIME_NODE";
289inline constexpr uint64_t kUsagePersistIntervalRows = 10'000;
293#if defined(SIGNET_EVAL_MAX_ROWS_MONTH_U64)
294inline constexpr uint64_t kDefaultEvalMaxRowsMonth =
295 static_cast<uint64_t
>(SIGNET_EVAL_MAX_ROWS_MONTH_U64);
297inline constexpr uint64_t kDefaultEvalMaxRowsMonth = 50'000'000ull;
302#if defined(SIGNET_EVAL_MAX_USERS_U32)
303inline constexpr uint32_t kDefaultEvalMaxUsers =
304 static_cast<uint32_t
>(SIGNET_EVAL_MAX_USERS_U32);
306inline constexpr uint32_t kDefaultEvalMaxUsers = 3u;
311#if defined(SIGNET_EVAL_MAX_NODES_U32)
312inline constexpr uint32_t kDefaultEvalMaxNodes =
313 static_cast<uint32_t
>(SIGNET_EVAL_MAX_NODES_U32);
315inline constexpr uint32_t kDefaultEvalMaxNodes = 1u;
320#if defined(SIGNET_EVAL_MAX_DAYS_U32)
321inline constexpr uint32_t kDefaultEvalMaxDays =
322 static_cast<uint32_t
>(SIGNET_EVAL_MAX_DAYS_U32);
324inline constexpr uint32_t kDefaultEvalMaxDays = 30u;
329#if defined(SIGNET_EVAL_WARN_PCT_1_U32)
330inline constexpr uint32_t kDefaultEvalWarnPct1 =
331 static_cast<uint32_t
>(SIGNET_EVAL_WARN_PCT_1_U32);
333inline constexpr uint32_t kDefaultEvalWarnPct1 = 80u;
338#if defined(SIGNET_EVAL_WARN_PCT_2_U32)
339inline constexpr uint32_t kDefaultEvalWarnPct2 =
340 static_cast<uint32_t
>(SIGNET_EVAL_WARN_PCT_2_U32);
342inline constexpr uint32_t kDefaultEvalWarnPct2 = 90u;
352struct LicensePolicy {
354 bool evaluation_mode{
false};
356 uint64_t max_rows_month{0};
358 uint32_t max_users{0};
360 uint32_t max_nodes{0};
362 int64_t explicit_expiry_day_utc{0};
364 uint32_t max_eval_days{0};
366 uint32_t warn_pct_1{kDefaultEvalWarnPct1};
368 uint32_t warn_pct_2{kDefaultEvalWarnPct2};
381 bool initialized{
false};
385 uint64_t rows_this_month{0};
387 uint64_t last_persisted_rows_this_month{0};
389 int64_t eval_start_day_utc{0};
391 bool warn_pct_1_emitted{
false};
393 bool warn_pct_2_emitted{
false};
395 std::unordered_set<std::string> users;
397 std::unordered_set<std::string> nodes;
406[[nodiscard]]
inline uint64_t fnv1a64(
const char* data,
size_t size)
noexcept {
407 constexpr uint64_t kOffset = 14695981039346656037ull;
408 constexpr uint64_t kPrime = 1099511628211ull;
410 uint64_t hash = kOffset;
411 for (
size_t i = 0; i < size; ++i) {
412 hash ^=
static_cast<uint8_t
>(data[i]);
419[[nodiscard]]
inline std::string trim_copy(
const std::string& in) {
421 while (start < in.size() && std::isspace(
static_cast<unsigned char>(in[start]))) {
425 size_t end = in.size();
426 while (end > start && std::isspace(
static_cast<unsigned char>(in[end - 1]))) {
430 return in.substr(start, end - start);
434[[nodiscard]]
inline std::string to_lower_ascii(std::string value) {
435 for (
char& ch : value) {
436 ch =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ch)));
442[[nodiscard]]
inline std::string sanitize_identity(std::string value,
443 const char* fallback) {
444 value = trim_copy(value);
449 if (value.size() > 128) {
453 for (
char& c : value) {
454 if (std::isalnum(
static_cast<unsigned char>(c)) ||
455 c ==
'-' || c ==
'_' || c ==
'.' || c ==
'@') {
464[[nodiscard]]
inline std::unordered_map<std::string, std::string>
465parse_claims(
const std::string& text) {
466 std::unordered_map<std::string, std::string> out;
467 std::stringstream ss(text);
470 while (std::getline(ss, token,
';')) {
471 auto pos = token.find(
'=');
472 if (pos == std::string::npos || pos == 0 || pos + 1 >= token.size()) {
475 std::string key = to_lower_ascii(trim_copy(token.substr(0, pos)));
476 std::string val = trim_copy(token.substr(pos + 1));
477 if (!key.empty() && !val.empty()) {
486[[nodiscard]]
inline bool parse_u64(
const std::string& text, uint64_t& out) {
487 if (text.empty())
return false;
490 for (
char ch : text) {
491 if (!std::isdigit(
static_cast<unsigned char>(ch)))
return false;
492 const uint64_t digit =
static_cast<uint64_t
>(ch -
'0');
493 if (value > ((std::numeric_limits<uint64_t>::max)() - digit) / 10ull) {
496 value = (value * 10ull) + digit;
504[[nodiscard]]
inline bool parse_u32(
const std::string& text, uint32_t& out) {
506 if (!parse_u64(text, value) || value > (std::numeric_limits<uint32_t>::max)()) {
509 out =
static_cast<uint32_t
>(value);
514[[nodiscard]]
inline constexpr int64_t days_from_civil(
int y,
unsigned m,
515 unsigned d)
noexcept {
517 const int era = (y >= 0 ? y : y - 399) / 400;
518 const unsigned yoe =
static_cast<unsigned>(y - era * 400);
519 const unsigned mp = (m > 2u) ? (m - 3u) : (m + 9u);
520 const unsigned doy = (153u * mp + 2u) / 5u + d - 1u;
521 const unsigned doe = yoe * 365u + yoe / 4u - yoe / 100u + doy;
522 return static_cast<int64_t
>(era) * 146097 +
static_cast<int64_t
>(doe) - 719468;
526[[nodiscard]]
inline bool is_leap_year(
int y)
noexcept {
527 return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
531[[nodiscard]]
inline bool parse_iso_date_to_epoch_day(
const std::string& iso,
533 if (iso.size() != 10 || iso[4] !=
'-' || iso[7] !=
'-')
return false;
535 auto parse_two = [&](
size_t idx,
int& out) ->
bool {
536 if (!std::isdigit(
static_cast<unsigned char>(iso[idx])) ||
537 !std::isdigit(
static_cast<unsigned char>(iso[idx + 1]))) {
540 out = (iso[idx] -
'0') * 10 + (iso[idx + 1] -
'0');
545 for (
size_t i = 0; i < 4; ++i) {
546 if (!std::isdigit(
static_cast<unsigned char>(iso[i])))
return false;
547 year = year * 10 + (iso[i] -
'0');
552 if (!parse_two(5, month) || !parse_two(8, day))
return false;
553 if (month < 1 || month > 12)
return false;
555 static constexpr int kMonthDays[] = {31,28,31,30,31,30,31,31,30,31,30,31};
556 int max_day = kMonthDays[month - 1];
557 if (month == 2 && is_leap_year(year)) max_day = 29;
558 if (day < 1 || day > max_day)
return false;
560 out_day = days_from_civil(year,
static_cast<unsigned>(month),
static_cast<unsigned>(day));
565[[nodiscard]]
inline int64_t current_epoch_day_utc() {
566 using namespace std::chrono;
567 const auto now_days = floor<days>(system_clock::now());
568 return now_days.time_since_epoch().count();
572[[nodiscard]]
inline int current_month_tag_utc() {
573 using namespace std::chrono;
574 const auto now_days = floor<days>(system_clock::now());
575 const year_month_day ymd{now_days};
576 return static_cast<int>(
static_cast<int>(ymd.year()) * 100 +
577 static_cast<unsigned>(ymd.month()));
584[[nodiscard]]
inline std::string default_usage_state_path() {
586 const char* xdg = std::getenv(
"XDG_STATE_HOME");
588 return std::string(xdg) +
"/signet-forge/usage_state";
591 const char* home = std::getenv(
"HOME");
593 if (!home || !home[0]) home = std::getenv(
"USERPROFILE");
595 if (home && home[0]) {
596 return std::string(home) +
"/.local/state/signet-forge/usage_state";
599#if defined(SIGNET_COMMERCIAL_LICENSE_HASH_U64)
601 std::snprintf(buf,
sizeof(buf),
602 "/tmp/signet_commercial_usage_%016llx.state",
603 static_cast<unsigned long long>(
604 static_cast<uint64_t
>(SIGNET_COMMERCIAL_LICENSE_HASH_U64)));
605 return std::string(buf);
607 return "/tmp/signet_commercial_usage.state";
615[[nodiscard]]
inline std::string usage_state_path() {
616 const char* env = std::getenv(kUsageFileEnvVar);
617 if (env !=
nullptr && env[0] !=
'\0') {
618 std::string raw(env);
621 if (raw.find(
"..") != std::string::npos)
622 return default_usage_state_path();
625 if (raw.find(
'\0') != std::string::npos)
626 return default_usage_state_path();
631 if (raw.empty() || raw[0] !=
'/')
632 return default_usage_state_path();
636 auto fs_path = std::filesystem::path(raw);
637 auto parent = fs_path.parent_path();
639 return default_usage_state_path();
641 char resolved[PATH_MAX] = {};
642 if (!::realpath(parent.string().c_str(), resolved))
643 return default_usage_state_path();
647 if (!std::filesystem::is_directory(resolved, ec))
648 return default_usage_state_path();
651 std::string sanitised = std::string(resolved) +
"/" +
652 fs_path.filename().string();
655 if (sanitised.find(
"..") != std::string::npos)
656 return default_usage_state_path();
664 return default_usage_state_path();
668[[nodiscard]]
inline std::string detect_runtime_user() {
669#if defined(SIGNET_ALLOW_RUNTIME_IDENTITY_ENV) && SIGNET_ALLOW_RUNTIME_IDENTITY_ENV
670 const char* env_user = std::getenv(kRuntimeUserEnvVar);
671 if (env_user !=
nullptr && env_user[0] !=
'\0') {
672 return sanitize_identity(std::string(env_user),
"unknown-user");
677 struct passwd pwd_buf {};
678 struct passwd* pwd =
nullptr;
679 char storage[4096] = {};
680 if (::getpwuid_r(::geteuid(), &pwd_buf, storage,
sizeof(storage), &pwd) == 0 &&
681 pwd !=
nullptr && pwd->pw_name !=
nullptr && pwd->pw_name[0] !=
'\0') {
682 return sanitize_identity(std::string(pwd->pw_name),
"unknown-user");
685 const char* user = std::getenv(
"USER");
686 if (user ==
nullptr || user[0] ==
'\0') {
687 user = std::getenv(
"LOGNAME");
690 const char* user = std::getenv(
"USERNAME");
692 return sanitize_identity(user ? std::string(user) : std::string(),
"unknown-user");
696[[nodiscard]]
inline std::string detect_runtime_node() {
697#if defined(SIGNET_ALLOW_RUNTIME_IDENTITY_ENV) && SIGNET_ALLOW_RUNTIME_IDENTITY_ENV
698 const char* env_node = std::getenv(kRuntimeNodeEnvVar);
699 if (env_node !=
nullptr && env_node[0] !=
'\0') {
700 return sanitize_identity(std::string(env_node),
"unknown-node");
706 if (::gethostname(host,
sizeof(host) - 1) == 0) {
707 host[
sizeof(host) - 1] =
'\0';
708 return sanitize_identity(std::string(host),
"unknown-node");
710 const char* node = std::getenv(
"HOSTNAME");
712 const char* node = std::getenv(
"COMPUTERNAME");
714 return sanitize_identity(node ? std::string(node) : std::string(),
"unknown-node");
718[[nodiscard]]
inline std::vector<std::string> split_csv(
const std::string& text) {
719 std::vector<std::string> out;
720 std::stringstream ss(text);
723 while (std::getline(ss, token,
',')) {
724 token = sanitize_identity(token,
"");
725 if (!token.empty()) {
726 out.push_back(token);
734[[nodiscard]]
inline std::string join_set_csv(
const std::unordered_set<std::string>& values) {
735 std::vector<std::string> ordered;
736 ordered.reserve(values.size());
737 for (
const auto& v : values) {
738 ordered.push_back(v);
740 std::sort(ordered.begin(), ordered.end());
743 for (
size_t i = 0; i < ordered.size(); ++i) {
744 if (i != 0) out.push_back(
',');
751inline void load_usage_state_from_file(
const std::string& path, UsageState& st) {
752 std::ifstream in(path);
758 while (std::getline(in, line)) {
759 auto eq = line.find(
'=');
760 if (eq == std::string::npos)
continue;
762 const std::string key = trim_copy(line.substr(0, eq));
763 const std::string val = trim_copy(line.substr(eq + 1));
766 if (key ==
"month_tag" && parse_u64(val, u64)) {
767 st.month_tag =
static_cast<int>(u64);
768 }
else if (key ==
"rows_this_month" && parse_u64(val, u64)) {
769 st.rows_this_month = u64;
770 }
else if (key ==
"eval_start_day_utc" && parse_u64(val, u64)) {
771 st.eval_start_day_utc =
static_cast<int64_t
>(u64);
772 }
else if (key ==
"warn_pct_1_emitted") {
773 st.warn_pct_1_emitted = (val ==
"1");
774 }
else if (key ==
"warn_pct_2_emitted") {
775 st.warn_pct_2_emitted = (val ==
"1");
776 }
else if (key ==
"users") {
777 for (
auto& token : split_csv(val)) {
778 st.users.insert(token);
780 }
else if (key ==
"nodes") {
781 for (
auto& token : split_csv(val)) {
782 st.nodes.insert(token);
787 st.last_persisted_rows_this_month = st.rows_this_month;
793[[nodiscard]]
inline bool persist_usage_state_to_file(
const std::string& path,
795 const std::string tmp_path = path +
".tmp";
801 if (::lstat(tmp_path.c_str(), &st_link) == 0 && S_ISLNK(st_link.st_mode)) {
804 if (::lstat(path.c_str(), &st_link) == 0 && S_ISLNK(st_link.st_mode)) {
812 int sfd = ::open(tmp_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
813 if (sfd < 0)
return false;
817 out.open(tmp_path, std::ios::out | std::ios::trunc);
819 out.open(tmp_path, std::ios::out | std::ios::trunc);
821 if (!out.is_open())
return false;
823 out <<
"month_tag=" << st.month_tag <<
'\n';
824 out <<
"rows_this_month=" << st.rows_this_month <<
'\n';
825 out <<
"eval_start_day_utc=" << st.eval_start_day_utc <<
'\n';
826 out <<
"warn_pct_1_emitted=" << (st.warn_pct_1_emitted ? 1 : 0) <<
'\n';
827 out <<
"warn_pct_2_emitted=" << (st.warn_pct_2_emitted ? 1 : 0) <<
'\n';
828 out <<
"users=" << join_set_csv(st.users) <<
'\n';
829 out <<
"nodes=" << join_set_csv(st.nodes) <<
'\n';
833 std::remove(tmp_path.c_str());
837 if (std::rename(tmp_path.c_str(), path.c_str()) != 0) {
838 std::remove(tmp_path.c_str());
842 st.last_persisted_rows_this_month = st.rows_this_month;
854[[nodiscard]]
inline LicensePolicy resolve_policy() {
855 LicensePolicy policy;
857 const char* license_key = std::getenv(kLicenseEnvVar);
858 const auto claims = parse_claims(license_key ? std::string(license_key) : std::string());
861 if (
auto it = claims.find(
"tier"); it != claims.end()) {
862 tier = to_lower_ascii(it->second);
864 const char* env_tier = std::getenv(kLicenseTierEnvVar);
865 if (env_tier !=
nullptr && env_tier[0] !=
'\0') {
866 tier = to_lower_ascii(env_tier);
870 policy.evaluation_mode =
871 (tier ==
"eval" || tier ==
"evaluation" || tier ==
"trial" ||
872 tier ==
"testing" || tier ==
"test");
874 if (!policy.evaluation_mode) {
878 policy.max_rows_month = kDefaultEvalMaxRowsMonth;
879 policy.max_users = kDefaultEvalMaxUsers;
880 policy.max_nodes = kDefaultEvalMaxNodes;
881 policy.max_eval_days = kDefaultEvalMaxDays;
882 policy.warn_pct_1 = kDefaultEvalWarnPct1;
883 policy.warn_pct_2 = kDefaultEvalWarnPct2;
885 auto set_u64 = [&](
const char* key, uint64_t& out) {
886 if (
auto it = claims.find(key); it != claims.end()) {
888 if (parse_u64(it->second, parsed)) out = parsed;
892 auto set_u32 = [&](
const char* key, uint32_t& out) {
893 if (
auto it = claims.find(key); it != claims.end()) {
895 if (parse_u32(it->second, parsed)) out = parsed;
899 set_u64(
"max_rows_month", policy.max_rows_month);
900 set_u64(
"rows_month", policy.max_rows_month);
901 set_u32(
"max_users", policy.max_users);
902 set_u32(
"max_nodes", policy.max_nodes);
903 set_u32(
"max_days", policy.max_eval_days);
904 set_u32(
"warn_pct_1", policy.warn_pct_1);
905 set_u32(
"warn_pct_2", policy.warn_pct_2);
907 if (
auto it = claims.find(
"expires_at"); it != claims.end()) {
909 if (parse_iso_date_to_epoch_day(it->second, day)) {
910 policy.explicit_expiry_day_utc = day;
914 if (policy.warn_pct_1 > 100u) policy.warn_pct_1 = 100u;
915 if (policy.warn_pct_2 > 100u) policy.warn_pct_2 = 100u;
916 if (policy.warn_pct_1 > policy.warn_pct_2) {
917 std::swap(policy.warn_pct_1, policy.warn_pct_2);
932#if !defined(SIGNET_ENABLE_COMMERCIAL) || !SIGNET_ENABLE_COMMERCIAL
934 "commercial feature disabled in this Apache build; "
935 "rebuild with -DSIGNET_ENABLE_COMMERCIAL=ON (AGPL-3.0 commercial tier)"};
937# if defined(SIGNET_REQUIRE_COMMERCIAL_LICENSE) && SIGNET_REQUIRE_COMMERCIAL_LICENSE
938# if !defined(SIGNET_COMMERCIAL_LICENSE_HASH_U64)
940 "commercial tier misconfigured: missing SIGNET_COMMERCIAL_LICENSE_HASH_U64"};
942 const char* license_key = std::getenv(kLicenseEnvVar);
943 if (license_key ==
nullptr || license_key[0] ==
'\0') {
945 std::string(
"missing runtime license key: set ") + kLicenseEnvVar};
948 constexpr uint64_t kExpectedHash =
949 static_cast<uint64_t
>(SIGNET_COMMERCIAL_LICENSE_HASH_U64);
950 const uint64_t actual_hash = fnv1a64(license_key, std::strlen(license_key));
952 if (actual_hash != kExpectedHash) {
954 std::string(
"invalid runtime license key in ") + kLicenseEnvVar};
964inline std::mutex& usage_state_mutex() {
970inline UsageState& usage_state() {
976inline void ensure_usage_state_loaded_locked(UsageState& st) {
977 if (st.initialized)
return;
979 load_usage_state_from_file(usage_state_path(), st);
980 if (st.month_tag == 0) {
981 st.month_tag = current_month_tag_utc();
983 st.initialized =
true;
1002[[nodiscard]]
inline expected<void> enforce_eval_limits(
const char* feature_name,
1003 uint64_t rows_increment) {
1004 static const LicensePolicy policy = resolve_policy();
1005 if (!policy.evaluation_mode) {
1009 std::lock_guard<std::mutex> lock(usage_state_mutex());
1010 UsageState& st = usage_state();
1011 ensure_usage_state_loaded_locked(st);
1013 bool persist_required =
false;
1015 const int current_month = current_month_tag_utc();
1016 if (st.month_tag != current_month) {
1017 st.month_tag = current_month;
1018 st.rows_this_month = 0;
1019 st.last_persisted_rows_this_month = 0;
1020 st.warn_pct_1_emitted =
false;
1021 st.warn_pct_2_emitted =
false;
1022 persist_required =
true;
1025 const int64_t today = current_epoch_day_utc();
1027 if (policy.max_eval_days > 0 && st.eval_start_day_utc == 0) {
1028 st.eval_start_day_utc = today;
1029 persist_required =
true;
1032 int64_t expiry_day = policy.explicit_expiry_day_utc;
1033 if (expiry_day == 0 && policy.max_eval_days > 0 && st.eval_start_day_utc > 0) {
1034 expiry_day = st.eval_start_day_utc +
static_cast<int64_t
>(policy.max_eval_days) - 1;
1037 if (expiry_day > 0 && today > expiry_day) {
1039 std::string(feature_name) +
1040 ": evaluation period expired; commercial license required"};
1043 const std::string user_id = detect_runtime_user();
1044 if (st.users.insert(user_id).second) {
1045 persist_required =
true;
1047 if (policy.max_users > 0 && st.users.size() > policy.max_users) {
1049 std::string(feature_name) +
1050 ": evaluation user threshold exceeded; commercial license required"};
1053 const std::string node_id = detect_runtime_node();
1054 if (st.nodes.insert(node_id).second) {
1055 persist_required =
true;
1057 if (policy.max_nodes > 0 && st.nodes.size() > policy.max_nodes) {
1059 std::string(feature_name) +
1060 ": evaluation node threshold exceeded; commercial license required"};
1063 if (policy.max_rows_month > 0) {
1064 if (rows_increment > 0) {
1065 if (st.rows_this_month >
1066 (std::numeric_limits<uint64_t>::max)() - rows_increment) {
1068 std::string(feature_name) +
1069 ": usage counter overflow; commercial license required"};
1072 const uint64_t projected = st.rows_this_month + rows_increment;
1073 if (projected > policy.max_rows_month) {
1075 std::string(feature_name) +
1076 ": evaluation monthly row threshold exceeded; "
1077 "commercial license required"};
1080 st.rows_this_month = projected;
1081 if ((st.rows_this_month - st.last_persisted_rows_this_month)
1082 >= kUsagePersistIntervalRows ||
1083 st.rows_this_month == policy.max_rows_month) {
1084 persist_required =
true;
1086 }
else if (st.rows_this_month >= policy.max_rows_month) {
1088 std::string(feature_name) +
1089 ": evaluation monthly row threshold reached; "
1090 "commercial license required"};
1093 const long double pct =
1094 (
static_cast<long double>(st.rows_this_month) * 100.0L) /
1095 static_cast<long double>(policy.max_rows_month);
1097 if (policy.warn_pct_1 > 0 && !st.warn_pct_1_emitted &&
1098 pct >=
static_cast<long double>(policy.warn_pct_1)) {
1099 std::fprintf(stderr,
1100 "signet commercial eval warning: %.2Lf%% of monthly row "
1101 "limit used (%llu/%llu)\n",
1103 static_cast<unsigned long long>(st.rows_this_month),
1104 static_cast<unsigned long long>(policy.max_rows_month));
1105 st.warn_pct_1_emitted =
true;
1106 persist_required =
true;
1109 if (policy.warn_pct_2 > 0 && !st.warn_pct_2_emitted &&
1110 pct >=
static_cast<long double>(policy.warn_pct_2)) {
1111 std::fprintf(stderr,
1112 "signet commercial eval warning: %.2Lf%% of monthly row "
1113 "limit used (%llu/%llu)\n",
1115 static_cast<unsigned long long>(st.rows_this_month),
1116 static_cast<unsigned long long>(policy.max_rows_month));
1117 st.warn_pct_2_emitted =
true;
1118 persist_required =
true;
1122 if (persist_required && !persist_usage_state_to_file(usage_state_path(), st)) {
1124 std::string(feature_name) +
1125 ": unable to persist evaluation usage state"};
1142[[nodiscard]]
inline expected<void> require_feature(
const char* feature_name,
1143 uint64_t usage_rows = 0) {
1147 std::string(feature_name) +
": " + gate.
error().
message};
1150 auto policy_gate = enforce_eval_limits(feature_name, usage_rows);
1152 return policy_gate.error();
1168[[nodiscard]]
inline expected<void> record_usage_rows(
const char* feature_name,
1170 return require_feature(feature_name, rows);