From ceab67f285cd5602fd5fd6e1037a7a5d12c369f5 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 17 Aug 2026 05:04:22 -0500 Subject: [PATCH] docs(teams): use the correct presentation of the project name, and match both MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Runic Gateway" (two words) is the correct presentation; "RunicGateway" is accepted only where the name has to condense to a single token — the Gitea org, a package name, a URL segment. Three prose uses in TEAMS.md had the condensed form for no reason, including the upload warning text, and are corrected. The `RunicGateway/` slugs throughout android/PLAN.md are condensed by necessity and are left alone. This turned out to matter beyond presentation. The reserved-name matcher (2.8) specified whole-word matching over a normalisation that case-folds, strips punctuation and collapses whitespace — under which "RunicGateway" is a SINGLE word and would never have matched the two-word term "Runic Gateway". The condensed form is the one an impersonator reaches for, precisely because it is what the org and every URL already use, so the check would have missed its most likely input. Multi-word terms are now additionally compared with whitespace removed on both sides, so `Runic Gateway` matches `RunicGateway`, `runic-gateway`, `Runic_Gateway` and `RUNIC GATEWAY` alike. The widening applies only to terms CONTAINING whitespace, which keeps it clear of the single-word terms where whole-word matching is doing the false-positive work: `admin` is still compared as a word and still does not fire on "Badminton". Listing the condensed form as a separate reserved term was the alternative and was rejected — it is a second thing to keep in sync and would still miss the hyphenated and underscored variants. The same reasoning applies to a deployment's own BRAND_NAME, which is free text and may well be spaced. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WnDSWzpUjw8t8C2hghysNz --- website/TEAMS.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/website/TEAMS.md b/website/TEAMS.md index a26760a..479a937 100644 --- a/website/TEAMS.md +++ b/website/TEAMS.md @@ -466,7 +466,7 @@ Two reads, OR'd. `viaGrant` is reported **even when membership also holds** — coexist; the UI presents membership as the current reason while the grant is kept as audit history" requirement, and it falls out of not collapsing the two booleans into one. -- A grant may name **any** RunicGateway account, including one with no linked game identity of any +- A grant may name **any** Runic Gateway account, including one with no linked game identity of any kind. That is the point: letting an unlinked guildmate into the forum must not be a staff ticket. - Granting **never** writes `team_members`. Revoking never writes `team_members`. A test asserts a byte-identical `team_members` row set across a grant/revoke cycle. @@ -505,7 +505,7 @@ Four hops, three of them stored in different places, and the design's most commo confusion — "this account is linked" answers only *one* of them. ``` -RunicGateway account users.id +Runic Gateway account users.id │ hop 1: linked GAME identity module-owned: shard_account_links(account → user_id) ▼ game account / character team_members.member_key, team_members.user_id @@ -569,6 +569,16 @@ runtime configuration): | The project | `Runic Gateway` — impersonating the software project is as much a problem as impersonating the operator | | Operator additions | a `teams_reserved_terms` setting, comma-separated, for anything a particular community needs | +> **The project's name has two legitimate presentations, and both must match.** **Runic Gateway** (two +> words) is the correct form; **RunicGateway** is accepted only where the name has to condense to a +> single token — the Gitea org, a package name, a URL segment. A reserved term is therefore stored in +> its correct two-word form and matched against *both*, which §2.8.2's whitespace-insensitive +> comparison gives for free. Listing the condensed form as a separate term would be a second thing to +> keep in sync, and it would still miss `runic-gateway` and `Runic_Gateway`. +> +> The same applies to a deployment's own brand: `BRAND_NAME` is free text and an operator may well +> have set a spaced name whose condensed form is what a would-be impersonator types. + **Not `filter_words`.** That table exists but is **bot-owned** (its own pool, `bot/src/model/`, never read by the website — `MODERATION_APPEALS.md` §2 is explicit that the two sides share no live FK) and it is a profanity filter, which is a different question with a different answer. Reusing it @@ -587,6 +597,18 @@ Normalisation: case-fold, strip punctuation and repeated characters, collapse wh **no leet-speak folding** in v1 (`4dm1n`) — it multiplies false positives, and the consequence of a miss is a hidden-by-a-human Team rather than a breach. +**A multi-word term is additionally compared with whitespace removed on both sides**, so a term stored +as `Runic Gateway` matches `RunicGateway`, `runic-gateway`, `Runic_Gateway` and `RUNIC GATEWAY` +alike. Without this the whole-word rule fails on exactly the case that matters: `RunicGateway` is a +*single* word and would never match a two-word term, so the condensed form — the one an impersonator +would reach for, because it is what the Gitea org and every URL already use — would sail straight +through. + +That widening applies **only to terms containing whitespace**, which keeps it away from the +single-word terms where whole-word matching is doing the false-positive work: `admin` is still +compared as a word and still does not fire on "Badminton". A two-word term is specific enough that +running the letters together cannot collide with ordinary vocabulary. + #### 2.8.3 What a match does: hide, never reject A Team whose name trips the list is created **normally** and then **auto-hidden**: `hidden = 1`, @@ -1175,7 +1197,7 @@ setting is, the second appears only at the moment of change and is what the ackn > - Complying with applicable laws and regulations > - Establishing policies for your community > -> RunicGateway does not provide hosted storage or content moderation services. All uploaded content +> Runic Gateway does not provide hosted storage or content moderation services. All uploaded content > is stored on your own infrastructure. **(b) Confirmation dialog** — shown only when changing the mode *to* `uploads`: