server/lib/tests/money/judge.rs
//! **The one judge of money words**, Gunnar's verbatim (`gunnar/crates/gunnar-server/tests/
//! open_build_speaks_no_money.rs`), shared by Holger's two guards: the server's literals
//! (`server/lib/tests/open_build_speaks_no_money.rs`) and the command line
//! (`server/cli/tests/open_cli_speaks_no_money.rs`). When Gunnar's list changes, this changes
//! in the same breath. Holger adds `unpaid` and a few technical words of its own, marked.
#![allow(dead_code)]
/// Words the open product has no use for. See the module docs.
const MONEY: &[&str] = &[
"price",
"prices",
"priced",
"pricing",
"pay",
"pays",
"paying",
"paid",
"payable",
"payment",
"payments",
"paywall",
"buy",
"buys",
"buying",
"bought",
"buyer",
"buyers",
"purchase",
"purchases",
"purchased",
"purchasing",
"sell",
"sells",
"selling",
"sold",
"seller",
"sellers",
"bill",
"bills",
"billed",
"billing",
"billable",
"invoice",
"invoices",
"invoicing",
"checkout",
"checkouts",
"money",
"currency",
"currencies",
"sek",
"eur",
"usd",
"stripe",
"plan",
"plans",
"quota",
"quotas",
"subscription",
"subscriptions",
"subscribe",
"renew",
"renews",
"renewal",
"renewals",
"refund",
"refunds",
"revenue",
// Holger's own (its first guard's list, 2026-09-15).
"unpaid",
];
/// Words that are money only when nothing in the same text makes them
/// technical. See the module docs for the rule.
const CONTEXTUAL: &[&str] = &[
"plan",
"plans",
"checkout",
"checkouts",
"renew",
"renews",
"renewal",
"renewals",
"subscription",
"subscriptions",
"subscribe",
];
/// What makes a [`CONTEXTUAL`] word technical: networking and DHCP, git's
/// worktree, the event bus (a `Watch` subscription, a replication or webhook
/// subscriber), the data-set grow and the console reset.
const TECHNICAL_CONTEXT: &[&str] = &[
// network / DHCP
"interface",
"interfaces",
"lease",
"leases",
"dhcp",
"dhcprequest",
"address",
"addresses",
"route",
"routes",
"network",
"net",
// the event bus
"event",
"events",
"bus",
"watch",
"watchrequest",
"replication",
"webhook",
"runner",
"subscriber",
"stream",
// git
"git",
"tree",
"worktree",
"commit",
"branch",
"clone",
"entry",
"build",
// grow / install / reset
"grow",
"growth",
"sectors",
"blocks",
"resize",
"layout",
"install",
"reset",
"passkeys",
// Holger's (A28 M13): a RETENTION plan, a MIGRATION plan and its dry run, the download
// ADMISSION plan, a CERTIFICATE renewal, a box's DOOR.
"retention",
"migration",
"dry",
"admission",
"admit",
"certificate",
"cert",
"door",
];
/// `"a lapsed tenant renews"` -> `["a", "lapsed", "tenant", "renews"]`.
pub fn words(text: &str) -> Vec<String> {
text.split(|c: char| !c.is_ascii_alphanumeric())
.filter(|w| !w.is_empty())
.map(|w| w.to_ascii_lowercase())
.collect()
}
/// **The one judge**: the money words in `text`, after the contextual rule.
pub fn leaks_in(text: &str) -> Vec<String> {
let ws = words(text);
let technical = ws.iter().any(|w| TECHNICAL_CONTEXT.contains(&w.as_str()));
let mut out = Vec::new();
for w in &ws {
if !MONEY.contains(&w.as_str()) {
continue;
}
if CONTEXTUAL.contains(&w.as_str()) && technical {
continue;
}
if w == "stripe"
&& !(text.contains("Stripe") || text.contains("STRIPE") || text.contains("stripe.com"))
{
continue;
}
out.push(w.clone());
}
out.sort();
out.dedup();
out
}