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
Site
What the bug would have been
Interface-name classification
awdl٣ / en٣ misclassified. Interface identity decides whether the UI says “direct” or “via router” — this project’s central honesty claim.
Pairing-code validation
A 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 sanitising
A 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 .otheronly 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:
Locale
Output for 2026-08-18
th_TH (buddhist)
2568-08-18
fa_IR
۱۴۰۴-۰۵-۲۷
ja_JP@japanese
0007-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.