Character.isNumber and CharacterSet.alphanumerics accept Arabic-Indic, Devanagari and fullwidth digits. This trap was hit three separate times in one codebase during the Aposemati build — it will recur.

The rule

Never validate a digit with isNumber alone. Always conjoin isASCII:

code.allSatisfy { $0.isASCII && $0.isNumber }

The $0.isASCII && is load-bearing, not defensive decoration. Verified by mutation: with it removed, en٣ (Arabic-Indic three) is accepted everywhere it was rejected.

The three sites in one project

SiteWhat the bug would have been
Interface-name classificationawdl٣ / en٣ misclassified. Interface identity decides whether the UI says “direct” or “via router” — this project’s central honesty claim.
Pairing-code validationA six-digit short authentication string containing non-ASCII digits. The digits the user compares on two screens must be the digits the code derives from.
File-extension sanitisingA peer-supplied extension of fullwidth or Arabic-Indic characters passing an alphanumeric filter into a Finder-visible folder.

Two ways to close it

Guard form — cheap, but must be remembered at every site:

guard $0.isASCII && $0.isNumber else { ... }

Structural form — better, and what the file-extension site ended up using: an allowlist Set<String> of permitted values. Set<String>.contains uses canonical NFC equality, so no fullwidth or Arabic-Indic form can equal an ASCII member. The trap is then unreachable rather than guarded against. Every hostile input in a 28-case sweep landed on the fallback dat; every output extension was ASCII.

Verify the conjunct is load-bearing

A guard that cannot be shown to change behaviour is a guard nobody will keep. The re-review here compiled the exact HEAD bytes in a standalone harness and confirmed en٣ lands in .other only because of isASCII. That is the level of evidence to demand — otherwise a later “simplification” deletes it.

The neighbouring locale trap

The same class of bug, different API. A DateFormatter without a fixed locale yields:

LocaleOutput for 2026-08-18
th_TH (buddhist)2568-08-18
fa_IR۱۴۰۴-۰۵-۲۷
ja_JP@japanese0007-08-18

Aposemati stores assets under ~/Pictures/Aposemati/YYYY-MM-DD/ and relies on lexicographic order being chronological order. Pin the formatter’s locale (or use ISO8601DateFormatter) — the implementation deviated from its brief on this and was right to.